Back out b2d134ef9d34, seems to have turned stuff orange. :-(
authorJeff Walden <jwalden@mit.edu>
Fri, 15 Jul 2011 17:04:52 -0700
changeset 72917 88c93f5b78e125e0e753c63d55ec98bf1fb1bdf1
parent 72916 b77da7cab114a5208bef05a66a33f3b675c327f8
child 72918 147edcfdda1105fa3eb819ee0fee758d17de2b2a
push idunknown
push userunknown
push dateunknown
milestone8.0a1
backs outb2d134ef9d346de10b1759e8471214aab95ebedc
Back out b2d134ef9d34, seems to have turned stuff orange. :-(
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,5 +1,4 @@
-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,16 +452,17 @@ 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:
@@ -573,17 +574,18 @@ 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_SETLOCAL:
+          case JSOP_FORLOCAL: {
             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_ITERNEXT. */
+    /* Location to stash the iteration value between JSOP_MOREITER and JSOP_FOR*. */
     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,16 +2017,17 @@ 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;
     }
@@ -2361,28 +2362,30 @@ 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);
@@ -2431,16 +2434,17 @@ 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;
@@ -4397,216 +4401,16 @@ 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;
@@ -5174,39 +4978,54 @@ 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
-             * was originally 'var x = i', the parser will have rewritten it;
-             * see Parser::forStatement. 'for (let x = i in o)' is mercifully
-             * banned.
+             * 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.
              */
-            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;
-            }
+            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;
 
             /* Compile the object expression to the right of 'in'. */
             {
                 TempPopScope tps;
-                if (forLet && !tps.popBlock(cx, cg))
+                if (type == TOK_LET && !tps.popBlock(cx, cg))
                     return JS_FALSE;
-                if (!js_EmitTree(cx, cg, pn2->pn_kid3))
+                if (!js_EmitTree(cx, cg, pn2->pn_right))
                     return JS_FALSE;
-                if (forLet && !tps.repushBlock(cx, cg))
+                if (type == TOK_LET && !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).
              */
@@ -5236,35 +5055,165 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             if (EmitTraceOp(cx, cg) < 0)
                 return JS_FALSE;
 
 #ifdef DEBUG
             intN loopDepth = cg->stackDepth;
 #endif
 
             /*
-             * 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)'.
+             * 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').
              */
-            if (!EmitAssignment(cx, cg, pn2->pn_kid2, JSOP_NOP, NULL))
-                return false;
+            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;
+            }
+
+            /* The stack should be balanced around the JSOP_FOR* opcode sequence. */
+            JS_ASSERT(cg->stackDepth == loopDepth);
+
             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;
-            }
-            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);
 
             /* 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 {
@@ -6153,18 +6102,190 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
             if (noteIndex < 0 ||
                 js_Emit1(cx, cg, JSOP_POP) < 0) {
                 return JS_FALSE;
             }
         }
         break;
 
       case TOK_ASSIGN:
-        if (!EmitAssignment(cx, cg, pn->pn_left, PN_OP(pn), pn->pn_right))
-            return false;
+        /*
+         * 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);
+        }
         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,37 +2570,105 @@ 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,41 +3098,44 @@ 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_GOTO:SRC_FOR_IN until
-                 * we hit a block local slot (note empty destructuring patterns
-                 * result in unit-count blocks).
+                 * Skip the for loop head stacked by JSOP_FORLOCAL until we hit
+                 * a block local slot (note empty destructuring patterns result
+                 * in unit-count blocks).
                  */
-                uintN pos = ss->top;
+                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)|.
                  */
-                uintN forpos = pos + 1;
+                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)
@@ -3164,17 +3167,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);
 
-                ptrdiff_t start = ss->offsets[pos];
+                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);
@@ -3218,57 +3221,28 @@ 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 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.
+                     * 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).
                      */
                     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,
@@ -3285,29 +3259,28 @@ 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 + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH);
+                        DECOMPILE_CODE(pc + next, cond - next);
                     } else {
                         /*
                          * As above, rval or an extension of it must remain
                          * stacked during loop body decompilation.
                          */
                         rval = GetStr(ss, ss->top - 1);
-                        xval = VarPrefix(js_GetSrcNote(jp->script, pc + next));
-                        js_printf(jp, "\t%s (%s%s in %s) {\n",
+                        js_printf(jp, "\t%s (%s in %s) {\n",
                                   foreach ? js_for_each_str : js_for_str,
-                                  xval, lval, rval);
+                                  lval, rval);
                         jp->indent += 4;
-                        DECOMPILE_CODE(pc + next + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH);
+                        DECOMPILE_CODE(pc + next, cond - next);
                         jp->indent -= 4;
                         js_printf(jp, "\t}\n");
                     }
 
                     pc += tail;
                     LOCAL_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX);
                     len = js_CodeSpec[*pc].length;
                     break;
@@ -3502,16 +3475,73 @@ 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,24 +60,23 @@ 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_SETPROP,
-     * JSOP_SETELEM, and comprehension-tails, respectively.  They are never
-     * stored in bytecode, so they don't preempt valid opcodes.
+     * 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.
      */
     JSOP_GETPROP2 = JSOP_LIMIT,
     JSOP_GETELEM2 = JSOP_LIMIT + 1,
-    JSOP_FORLOCAL = JSOP_LIMIT + 2,
-    JSOP_FAKE_LIMIT = JSOP_FORLOCAL
+    JSOP_FAKE_LIMIT = JSOP_GETELEM2
 } 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,18 +120,19 @@ 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)
 
-OPDEF(JSOP_SWAP,      10, "swap",       NULL,         1,  2,  2,  0,  JOF_BYTE)
-OPDEF(JSOP_POPN,      11, "popn",       NULL,         3, -1,  0,  0,  JOF_UINT16)
+/* 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)
 
 /* 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)
@@ -213,20 +214,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_ITERNEXT,  77, "iternext",   "<next>",     2,  0,  1,  0,  JOF_UINT8)
-OPDEF(JSOP_ENDITER,   78, "enditer",    NULL,         1,  1,  0,  0,  JOF_BYTE)
+OPDEF(JSOP_ENDITER,   77, "enditer",    NULL,         1,  1,  0,  0,  JOF_BYTE)
 
-OPDEF(JSOP_FUNAPPLY,  79, "funapply",   NULL,         3, -1,  1, 18,  JOF_UINT16|JOF_INVOKE)
+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)
 
 /* 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. */
@@ -269,25 +270,21 @@ 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)
 
-/* 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 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)
 
 /* 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)
 
@@ -584,17 +581,29 @@ 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,     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)
+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_SHARPINIT,     233,"sharpinit",     NULL,  3,  0,  0,  0,  JOF_UINT16|JOF_SHARPSLOT)
+/* 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)
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -695,29 +695,22 @@ 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;
-    pn->init(type, op, arity);
-    pn->pn_pos = pos;
+    const Token &tok = tc->parser->tokenStream.currentToken();
+    pn->init(tok.type, JSOP_NOP, arity);
+    pn->pn_pos = tok.pos;
     return pn;
 }
 
 JSParseNode *
 JSParseNode::newBinaryOrAppend(TokenKind tt, JSOp op, JSParseNode *left, JSParseNode *right,
                                JSTreeContext *tc)
 {
     JSParseNode *pn, *pn1, *pn2;
@@ -780,17 +773,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 pn;
+    return (BinaryNode *)pn;
 }
 
 namespace js {
 
 inline void
 NameNode::initCommon(JSTreeContext *tc)
 {
     pn_expr = NULL;
@@ -3573,25 +3566,16 @@ 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;
 
     /*
@@ -4389,18 +4373,16 @@ 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;
@@ -4902,96 +4884,16 @@ 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
@@ -5016,20 +4918,18 @@ 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) {
-            reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
-            return NULL;
-        }
+        if (pn->pn_iflags & JSITER_FOREACH)
+            goto bad_for_each;
 
         /* 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
@@ -5070,28 +4970,17 @@ 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
@@ -5120,41 +5009,35 @@ 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;
         }
 
-        /*
-         * 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;
+        /* pn2 points to the name or destructuring pattern on in's left. */
+        JSParseNode *pn2 = NULL;
         uintN dflag = PND_ASSIGNED;
+
         if (TokenKindIsDecl(tt)) {
-            /* Tell EmitVariables that pn1 is part of a for/in. */
+            /* Tell js_EmitTree(TOK_VAR) 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);
@@ -5174,53 +5057,65 @@ 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) {
-                    pn2 = pn2->pn_left;
-                    JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC ||
-                              pn2->pn_type == TOK_NAME);
-                }
+                    pn1 = CloneParseTree(pn2->pn_left, tc);
+                    if (!pn1)
+                        return NULL;
+                } else
 #endif
-                pn1 = NULL;
-            }
-
-            /*
-             * pn2 is part of a declaration. Make a copy that can be passed to
-             * EmitAssignment.
-             */
-            pn2 = CloneLeftHandSide(pn2, tc);
-            if (!pn2)
+                {
+                    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) {
+            pn2 = pn1;
+            if (pn2->pn_type == TOK_LP &&
+                !MakeSetCall(context, pn2, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) {
                 return NULL;
-        } else {
-            /* Not a declaration. */
-            pn2 = pn1;
-            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:
-            JS_NOT_REACHED("forStatement TOK_ASSIGN");
-            break;
-
+            pn2 = pn2->pn_left;
+            JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC);
+            /* FALL THROUGH */
           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;
@@ -5236,63 +5131,71 @@ 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
-        pn3 = expr();
+        pn2 = expr();
 #if JS_HAS_BLOCK_SCOPE
         if (let)
             tc->topStmt = save;
 #endif
 
-        pn4->pn_type = TOK_IN;
+        pn2 = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, pn1, pn2, tc);
+        if (!pn2)
+            return NULL;
+        pn->pn_left = pn2;
     } else {
-        if (pn->pn_iflags & JSITER_FOREACH) {
-            reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
-            return NULL;
-        }
+        if (pn->pn_iflags & JSITER_FOREACH)
+            goto bad_for_each;
         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;
 
@@ -5305,16 +5208,20 @@ 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;
 
     /*
@@ -6130,17 +6037,18 @@ 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;
 
@@ -6166,18 +6074,19 @@ 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)
@@ -6451,62 +6360,16 @@ 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);
@@ -6517,23 +6380,62 @@ Parser::assignExpr()
         return NULL;
 
     if (!tokenStream.isCurrentTokenType(TOK_ASSIGN)) {
         tokenStream.ungetToken();
         return pn;
     }
 
     JSOp op = tokenStream.currentToken().t_op;
-    if (!setAssignmentLhsOps(pn, 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;
+    }
 
     JSParseNode *rhs = assignExpr();
-    if (!rhs)
-        return NULL;
-    if (PN_TYPE(pn) == TOK_NAME && pn->pn_used) {
+    if (rhs && 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.
@@ -7229,36 +7131,17 @@ Parser::comprehensionTail(JSParseNode *k
             data.pn = pn3;
             if (!data.binder(context, &data, atom, tc))
                 return NULL;
             break;
 
           default:;
         }
 
-        /*
-         * 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);
+        pn2->pn_left = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, 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);
@@ -8464,33 +8347,34 @@ 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. These ops 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, and iterated with
+             * JSOP_FORLOCAL.  These ops all 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,24 +100,21 @@ 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 ternary TOK_IN node with
- *                              pn_kid1:  TOK_VAR to left of 'in', or NULL
- *                                its pn_xflags may have PNX_POPVAR
+ *                            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
  *                                and PNX_FORINVAR bits set
- *                              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'
+ *                              pn_right: 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
@@ -459,26 +456,21 @@ 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);
@@ -708,48 +700,22 @@ 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);
@@ -1265,18 +1231,16 @@ 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 (!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) &&
+            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) &&
                    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,32 +2203,59 @@ 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 *loop = prelude->pn_next;
-
-        LOCAL_ASSERT(PN_TYPE(prelude) == TOK_VAR && PN_TYPE(loop) == TOK_FOR);
-
+        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;
         Value var;
-        if (!variableDeclaration(prelude, false, &var))
-            return false;
+
+        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;
+            }
+        }
 
         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_kid3, &expr) &&
+        return expression(head->pn_right, &expr) &&
                statement(loop->pn_right, &stmt) &&
                builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst);
       }
 
       case TOK_BREAK:
       case TOK_CONTINUE:
       {
         Value label;
@@ -2323,18 +2350,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_kid2, NULL, &patt) &&
-           expression(in->pn_kid3, &src) &&
+    return pattern(in->pn_left, NULL, &patt) &&
+           expression(in->pn_right, &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_ITERNEXT already guards on this, but in certain rare cases we might
+     * JSOP_FOR* 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,26 +14889,16 @@ 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;
@@ -14948,18 +14938,19 @@ 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(Value &iterobj_val, LIns* &v_ins)
-{
+TraceRecorder::unboxNextValue(LIns* &v_ins)
+{
+    Value &iterobj_val = stackval(-1);
     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);
@@ -15009,16 +15000,70 @@ TraceRecorder::unboxNextValue(Value &ite
     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,18 +1369,17 @@ 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(Value &iterobj_val,
-                                                              nanojit::LIns* &v_ins);
+    JS_REQUIRES_STACK AbortableRecordingStatus unboxNextValue(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 - 92)
+#define JSXDR_BYTECODE_VERSION      (0xb973c0de - 91)
 
 /*
  * 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,19 +1027,27 @@ mjit::Compiler::generateMethod()
              */
             if (canUseApplyTricks())
                 applyTricks = LazyArgsObj;
             else
                 jsop_arguments();
             frame.pushSynced();
           END_CASE(JSOP_ARGUMENTS)
 
-          BEGIN_CASE(JSOP_ITERNEXT)
-            iterNext(GET_INT8(PC));
-          END_CASE(JSOP_ITERNEXT)
+          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_DUP)
             frame.dup();
           END_CASE(JSOP_DUP)
 
           BEGIN_CASE(JSOP_DUP2)
             frame.dup2();
           END_CASE(JSOP_DUP2)
@@ -1651,16 +1659,34 @@ 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)
@@ -4101,23 +4127,23 @@ mjit::Compiler::iter(uintN flags)
     frame.pushTypedPayload(JSVAL_TYPE_OBJECT, ioreg);
 
     stubcc.rejoin(Changes(1));
 
     return true;
 }
 
 /*
- * 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.
+ * 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.
  */
 void
-mjit::Compiler::iterNext(ptrdiff_t offset)
+mjit::Compiler::iterNext()
 {
-    FrameEntry *fe = frame.peek(-offset);
+    FrameEntry *fe = frame.peek(-1);
     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 */
@@ -4151,17 +4177,16 @@ mjit::Compiler::iterNext(ptrdiff_t offse
     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));
 }
 
@@ -4986,8 +5011,70 @@ 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(ptrdiff_t offset);
+    void iterNext();
     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,16 +471,19 @@ 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, int32 offset)
+stubs::IterNext(VMFrame &f)
 {
-    JS_ASSERT(f.regs.sp - offset >= f.fp()->base());
-    JS_ASSERT(f.regs.sp[-offset].isObject());
+    JS_ASSERT(f.regs.sp - 1 >= f.fp()->base());
+    JS_ASSERT(f.regs.sp[-1].isObject());
 
-    JSObject *iterobj = &f.regs.sp[-offset].toObject();
+    JSObject *iterobj = &f.regs.sp[-1].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, int32 offset);
+void JS_FASTCALL IterNext(VMFrame &f);
 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);