Bug 1456404 - Part 2: Add CForEmitter. r=jwalden
authorTooru Fujisawa <arai_a@mac.com>
Fri, 10 Aug 2018 07:49:17 +0900
changeset 486017 6dbce3046bd95e61ced3487cb3d6e5bf4310983f
parent 486016 e20a86a9b5756d9bf2ec3c48b1c7ddbdb95a9eba
child 486018 b4a44d463eaaca925d705d6145f24d9f18c59a7a
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1456404
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1456404 - Part 2: Add CForEmitter. r=jwalden
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/CForEmitter.cpp
js/src/frontend/CForEmitter.h
js/src/frontend/EmitterScope.cpp
js/src/frontend/EmitterScope.h
js/src/frontend/SourceNotes.h
js/src/jit/IonControlFlow.cpp
js/src/moz.build
js/src/shell/js.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -20,16 +20,17 @@
 
 #include "jsapi.h"
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.h"
+#include "frontend/CForEmitter.h"
 #include "frontend/EmitterScope.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
 #include "frontend/Parser.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "vm/BytecodeUtil.h"
@@ -4780,17 +4781,17 @@ BytecodeEmitter::emitInitializeForInOrOf
                "for-in/of loop destructuring declarations can't have initializers");
 
     MOZ_ASSERT(target->isKind(ParseNodeKind::Array) ||
                target->isKind(ParseNodeKind::Object));
     return emitDestructuringOps(target, DestructuringDeclaration);
 }
 
 bool
-BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitterScope)
+BytecodeEmitter::emitForOf(ParseNode* forOfLoop, const EmitterScope* headLexicalEmitterScope)
 {
     MOZ_ASSERT(forOfLoop->isKind(ParseNodeKind::For));
     MOZ_ASSERT(forOfLoop->isArity(PN_BINARY));
 
     ParseNode* forOfHead = forOfLoop->pn_left;
     MOZ_ASSERT(forOfHead->isKind(ParseNodeKind::ForOf));
     MOZ_ASSERT(forOfHead->isArity(PN_TERNARY));
 
@@ -4971,17 +4972,17 @@ BytecodeEmitter::emitForOf(ParseNode* fo
     {
         return false;
     }
 
     return emitPopN(3);                                   //
 }
 
 bool
-BytecodeEmitter::emitForIn(ParseNode* forInLoop, EmitterScope* headLexicalEmitterScope)
+BytecodeEmitter::emitForIn(ParseNode* forInLoop, const EmitterScope* headLexicalEmitterScope)
 {
     MOZ_ASSERT(forInLoop->isKind(ParseNodeKind::For));
     MOZ_ASSERT(forInLoop->isArity(PN_BINARY));
     MOZ_ASSERT(forInLoop->isOp(JSOP_ITER));
 
     ParseNode* forInHead = forInLoop->pn_left;
     MOZ_ASSERT(forInHead->isKind(ParseNodeKind::ForIn));
     MOZ_ASSERT(forInHead->isArity(PN_TERNARY));
@@ -5120,203 +5121,92 @@ BytecodeEmitter::emitForIn(ParseNode* fo
     if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, loopInfo.headOffset(), offset()))
         return false;
 
     return emit1(JSOP_ENDITER);                           //
 }
 
 /* C-style `for (init; cond; update) ...` loop. */
 bool
-BytecodeEmitter::emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope)
-{
-    LoopControl loopInfo(this, StatementKind::ForLoop);
-
+BytecodeEmitter::emitCStyleFor(ParseNode* pn, const EmitterScope* headLexicalEmitterScope)
+{
     ParseNode* forHead = pn->pn_left;
     ParseNode* forBody = pn->pn_right;
+    ParseNode* init = forHead->pn_kid1;
+    ParseNode* cond = forHead->pn_kid2;
+    ParseNode* update = forHead->pn_kid3;
+    bool isLet = init && init->isKind(ParseNodeKind::Let);
+
+    CForEmitter cfor(this, isLet ? headLexicalEmitterScope : nullptr);
+
+    if (!cfor.emitInit(init ? Some(init->pn_pos.begin) : Nothing()))
+        return false;                                     //
 
     // If the head of this for-loop declared any lexical variables, the parser
     // wrapped this ParseNodeKind::For node in a ParseNodeKind::LexicalScope
-    // representing the implicit scope of those variables. By the time we get here,
-    // we have already entered that scope. So far, so good.
-    //
-    // ### Scope freshening
-    //
-    // Each iteration of a `for (let V...)` loop creates a fresh loop variable
-    // binding for V, even if the loop is a C-style `for(;;)` loop:
-    //
-    //     var funcs = [];
-    //     for (let i = 0; i < 2; i++)
-    //         funcs.push(function() { return i; });
-    //     assertEq(funcs[0](), 0);  // the two closures capture...
-    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
-    //
-    // This is implemented by "freshening" the implicit block -- changing the
-    // scope chain to a fresh clone of the instantaneous block object -- each
-    // iteration, just before evaluating the "update" in for(;;) loops.
-    //
-    // No freshening occurs in `for (const ...;;)` as there's no point: you
-    // can't reassign consts. This is observable through the Debugger API. (The
-    // ES6 spec also skips cloning the environment in this case.)
-    bool forLoopRequiresFreshening = false;
-    if (ParseNode* init = forHead->pn_kid1) {
+    // representing the implicit scope of those variables. By the time we get
+    // here, we have already entered that scope. So far, so good.
+    if (init) {
         // Emit the `init` clause, whether it's an expression or a variable
         // declaration. (The loop variables were hoisted into an enclosing
         // scope, but we still need to emit code for the initializers.)
-        if (!updateSourceCoordNotes(init->pn_pos.begin))
-            return false;
         if (init->isForLoopDeclaration()) {
-            if (!emitTree(init))
+            if (!emitTree(init))                          //
                 return false;
         } else {
             // 'init' is an expression, not a declaration. emitTree left its
             // value on the stack.
-            if (!emitTree(init, ValueUsage::IgnoreValue))
-                return false;
-            if (!emit1(JSOP_POP))
-                return false;
-        }
-
-        // ES 13.7.4.8 step 2. The initial freshening.
-        //
-        // If an initializer let-declaration may be captured during loop iteration,
-        // the current scope has an environment.  If so, freshen the current
-        // environment to expose distinct bindings for each loop iteration.
-        forLoopRequiresFreshening = init->isKind(ParseNodeKind::Let) && headLexicalEmitterScope;
-        if (forLoopRequiresFreshening) {
-            // The environment chain only includes an environment for the for(;;)
-            // loop head's let-declaration *if* a scope binding is captured, thus
-            // requiring a fresh environment each iteration. If a lexical scope
-            // exists for the head, it must be the innermost one. If that scope
-            // has closed-over bindings inducing an environment, recreate the
-            // current environment.
-            MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
-            MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
-
-            if (headLexicalEmitterScope->hasEnvironment()) {
-                if (!emit1(JSOP_FRESHENLEXICALENV))
-                    return false;
-            }
-        }
-    }
-
-    /*
-     * NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
-     * Use tmp to hold the biased srcnote "top" offset, which differs
-     * from the top local variable by the length of the JSOP_GOTO
-     * emitted in between tmp and top if this loop has a condition.
-     */
-    unsigned noteIndex;
-    if (!newSrcNote(SRC_FOR, &noteIndex))
-        return false;
-    if (!emit1(JSOP_NOP))
-        return false;
-    ptrdiff_t top = offset();
-
-    if (forHead->pn_kid2) {
-        /* Goto the loop condition, which branches back to iterate. */
-        if (!loopInfo.emitEntryJump(this))
-            return false;
-    }
-
-    /* Emit code for the loop body. */
-    if (!loopInfo.emitLoopHead(this, getOffsetForLoop(forBody)))
-        return false;
-    if (!forHead->pn_kid2) {
-        if (!loopInfo.emitLoopEntry(this, getOffsetForLoop(forBody)))
-            return false;
-    }
-
-    if (!emitTreeInBranch(forBody))
-        return false;
-
-    // Set loop and enclosing "update" offsets, for continue.  Note that we
-    // continue to immediately *before* the block-freshening: continuing must
-    // refresh the block.
-    if (!loopInfo.emitContinueTarget(this))
-        return false;
-
-    // ES 13.7.4.8 step 3.e. The per-iteration freshening.
-    if (forLoopRequiresFreshening) {
-        MOZ_ASSERT(headLexicalEmitterScope == innermostEmitterScope());
-        MOZ_ASSERT(headLexicalEmitterScope->scope(this)->kind() == ScopeKind::Lexical);
-
-        if (headLexicalEmitterScope->hasEnvironment()) {
-            if (!emit1(JSOP_FRESHENLEXICALENV))
-                return false;
-        }
+            if (!emitTree(init, ValueUsage::IgnoreValue)) // VAL
+                return false;
+            if (!emit1(JSOP_POP))                         //
+                return false;
+        }
+    }
+
+    if (!cfor.emitBody(cond ? CForEmitter::Cond::Present : CForEmitter::Cond::Missing,
+                       getOffsetForLoop(forBody)))        //
+    {
+        return false;
+    }
+
+    if (!emitTree(forBody))                               //
+        return false;
+
+    if (!cfor.emitUpdate(update ? CForEmitter::Update::Present : CForEmitter::Update::Missing,
+                         update ? Some(update->pn_pos.begin) : Nothing()))
+    {                                                     //
+        return false;
     }
 
     // Check for update code to do before the condition (if any).
-    // The update code may not be executed at all; it needs its own TDZ cache.
-    if (ParseNode* update = forHead->pn_kid3) {
-        TDZCheckCache tdzCache(this);
-
-        if (!updateSourceCoordNotes(update->pn_pos.begin))
-            return false;
-        if (!emitTree(update, ValueUsage::IgnoreValue))
-            return false;
-        if (!emit1(JSOP_POP))
-            return false;
-
-        /* Restore the absolute line number for source note readers. */
-        uint32_t lineNum = parser->errorReporter().lineAt(pn->pn_pos.end);
-        if (currentLine() != lineNum) {
-            if (!newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum)))
-                return false;
-            current->currentLine = lineNum;
-            current->lastColumn = 0;
-        }
-    }
-
-    ptrdiff_t tmp3 = offset();
-
-    if (forHead->pn_kid2) {
-        /* Fix up the goto from top to target the loop condition. */
-        if (!loopInfo.emitLoopEntry(this, getOffsetForLoop(forHead->pn_kid2)))
-            return false;
-
-        if (!emitTree(forHead->pn_kid2))
-            return false;
-    } else if (!forHead->pn_kid3) {
-        // If there is no condition clause and no update clause, mark
-        // the loop-ending "goto" with the location of the "for".
-        // This ensures that the debugger will stop on each loop
-        // iteration.
-        if (!updateSourceCoordNotes(pn->pn_pos.begin))
-            return false;
-    }
-
-    /* Set the first note offset so we can find the loop condition. */
-    if (!setSrcNoteOffset(noteIndex, 0, tmp3 - top))
-        return false;
-    if (!setSrcNoteOffset(noteIndex, 1, loopInfo.continueTargetOffset() - top))
-        return false;
-
-    /* If no loop condition, just emit a loop-closing jump. */
-    if (!loopInfo.emitLoopEnd(this, forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO))
-        return false;
-
-    /* The third note offset helps us find the loop-closing jump. */
-    if (!setSrcNoteOffset(noteIndex, 2, loopInfo.loopEndOffset() - top))
-        return false;
-
-    if (!tryNoteList.append(JSTRY_LOOP, stackDepth, loopInfo.headOffset(),
-                            loopInfo.breakTargetOffset()))
+    if (update) {
+        if (!emitTree(update, ValueUsage::IgnoreValue))   // VAL
+            return false;
+    }
+
+    if (!cfor.emitCond(Some(pn->pn_pos.begin),
+                       cond ? Some(cond->pn_pos.begin) : Nothing(),
+                       Some(pn->pn_pos.end)))             //
     {
         return false;
     }
 
-    if (!loopInfo.patchBreaksAndContinues(this))
+    if (cond) {
+        if (!emitTree(cond))                              // VAL
+            return false;
+    }
+
+    if (!cfor.emitEnd())                                  //
         return false;
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope)
+BytecodeEmitter::emitFor(ParseNode* pn, const EmitterScope* headLexicalEmitterScope)
 {
     MOZ_ASSERT(pn->isKind(ParseNodeKind::For));
 
     if (pn->pn_left->isKind(ParseNodeKind::ForHead))
         return emitCStyleFor(pn, headLexicalEmitterScope);
 
     if (!updateLineNumberNotes(pn->pn_pos.begin))
         return false;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -812,20 +812,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitSelfHostedAllowContentIter(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(ParseNode* pn);
     MOZ_MUST_USE bool emitSelfHostedHasOwn(ParseNode* pn);
 
     MOZ_MUST_USE bool emitDo(ParseNode* pn);
     MOZ_MUST_USE bool emitWhile(ParseNode* pn);
 
-    MOZ_MUST_USE bool emitFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope = nullptr);
-    MOZ_MUST_USE bool emitCStyleFor(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
-    MOZ_MUST_USE bool emitForIn(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
-    MOZ_MUST_USE bool emitForOf(ParseNode* pn, EmitterScope* headLexicalEmitterScope);
+    MOZ_MUST_USE bool emitFor(ParseNode* pn,
+                              const EmitterScope* headLexicalEmitterScope = nullptr);
+    MOZ_MUST_USE bool emitCStyleFor(ParseNode* pn, const EmitterScope* headLexicalEmitterScope);
+    MOZ_MUST_USE bool emitForIn(ParseNode* pn, const EmitterScope* headLexicalEmitterScope);
+    MOZ_MUST_USE bool emitForOf(ParseNode* pn, const EmitterScope* headLexicalEmitterScope);
 
     MOZ_MUST_USE bool emitInitializeForInOrOfTarget(ParseNode* forHead);
 
     MOZ_MUST_USE bool emitBreak(PropertyName* label);
     MOZ_MUST_USE bool emitContinue(PropertyName* label);
 
     MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ParseNode* pn);
     MOZ_MUST_USE bool emitFunctionFormalParameters(ParseNode* pn);
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CForEmitter.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "frontend/CForEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/EmitterScope.h"
+#include "frontend/SourceNotes.h"
+#include "vm/Opcodes.h"
+#include "vm/Scope.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+CForEmitter::CForEmitter(BytecodeEmitter* bce,
+                         const EmitterScope* headLexicalEmitterScopeForLet)
+  : bce_(bce),
+    headLexicalEmitterScopeForLet_(headLexicalEmitterScopeForLet)
+{}
+
+bool
+CForEmitter::emitInit(const Maybe<uint32_t>& initPos)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    loopInfo_.emplace(bce_, StatementKind::ForLoop);
+
+    if (initPos) {
+        if (!bce_->updateSourceCoordNotes(*initPos))
+            return false;
+    }
+
+#ifdef DEBUG
+    state_ = State::Init;
+#endif
+    return true;
+}
+
+bool
+CForEmitter::emitBody(Cond cond, const Maybe<uint32_t>& bodyPos)
+{
+    MOZ_ASSERT(state_ == State::Init);
+    cond_ = cond;
+
+    // ES 13.7.4.8 step 2. The initial freshening.
+    //
+    // If an initializer let-declaration may be captured during loop
+    // iteration, the current scope has an environment.  If so, freshen the
+    // current environment to expose distinct bindings for each loop
+    // iteration.
+    if (headLexicalEmitterScopeForLet_) {
+        // The environment chain only includes an environment for the
+        // for(;;) loop head's let-declaration *if* a scope binding is
+        // captured, thus requiring a fresh environment each iteration. If
+        // a lexical scope exists for the head, it must be the innermost
+        // one. If that scope has closed-over bindings inducing an
+        // environment, recreate the current environment.
+        MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope());
+        MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_)->kind() == ScopeKind::Lexical);
+
+        if (headLexicalEmitterScopeForLet_->hasEnvironment()) {
+            if (!bce_->emit1(JSOP_FRESHENLEXICALENV))
+                return false;
+        }
+    }
+
+    // NB: the SRC_FOR note has offsetBias 1 (JSOP_NOP_LENGTH).
+    if (!bce_->newSrcNote(SRC_FOR, &noteIndex_))
+        return false;
+    if (!bce_->emit1(JSOP_NOP))
+        return false;
+
+    biasedTop_ = bce_->offset();
+
+    if (cond_ == Cond::Present) {
+        // Goto the loop condition, which branches back to iterate.
+        if (!loopInfo_->emitEntryJump(bce_))
+            return false;
+    }
+
+    if (!loopInfo_->emitLoopHead(bce_, bodyPos))
+        return false;
+
+    if (cond_ == Cond::Missing) {
+        if (!loopInfo_->emitLoopEntry(bce_, bodyPos))
+            return false;
+    }
+
+    tdzCache_.emplace(bce_);
+
+#ifdef DEBUG
+    state_ = State::Body;
+#endif
+    return true;
+}
+
+bool
+CForEmitter::emitUpdate(Update update, const Maybe<uint32_t>& updatePos)
+{
+    MOZ_ASSERT(state_ == State::Body);
+    update_ = update;
+    tdzCache_.reset();
+
+    // Set loop and enclosing "update" offsets, for continue.  Note that we
+    // continue to immediately *before* the block-freshening: continuing must
+    // refresh the block.
+    if (!loopInfo_->emitContinueTarget(bce_))
+        return false;
+
+    // ES 13.7.4.8 step 3.e. The per-iteration freshening.
+    if (headLexicalEmitterScopeForLet_) {
+        MOZ_ASSERT(headLexicalEmitterScopeForLet_ == bce_->innermostEmitterScope());
+        MOZ_ASSERT(headLexicalEmitterScopeForLet_->scope(bce_)->kind() == ScopeKind::Lexical);
+
+        if (headLexicalEmitterScopeForLet_->hasEnvironment()) {
+            if (!bce_->emit1(JSOP_FRESHENLEXICALENV))
+                return false;
+        }
+    }
+
+    // The update code may not be executed at all; it needs its own TDZ
+    // cache.
+    if (update_ == Update::Present) {
+        tdzCache_.emplace(bce_);
+
+        if (updatePos) {
+            if (!bce_->updateSourceCoordNotes(*updatePos))
+                return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Update;
+#endif
+    return true;
+}
+
+bool
+CForEmitter::emitCond(const Maybe<uint32_t>& forPos,
+                      const Maybe<uint32_t>& condPos,
+                      const Maybe<uint32_t>& endPos)
+{
+    MOZ_ASSERT(state_ == State::Update);
+
+    if (update_ == Update::Present) {
+        if (!bce_->emit1(JSOP_POP))                   //
+            return false;
+
+        // Restore the absolute line number for source note readers.
+        if (endPos) {
+            uint32_t lineNum =
+                bce_->parser->errorReporter().lineAt(*endPos);
+            if (bce_->currentLine() != lineNum) {
+                if (!bce_->newSrcNote2(SRC_SETLINE, ptrdiff_t(lineNum)))
+                    return false;
+                bce_->current->currentLine = lineNum;
+                bce_->current->lastColumn = 0;
+            }
+        }
+    }
+
+    if (update_ == Update::Present)
+        tdzCache_.reset();
+
+    condOffset_ = bce_->offset();
+
+    if (cond_ == Cond::Present) {
+        if (!loopInfo_->emitLoopEntry(bce_, condPos))
+            return false;
+    } else if (update_ == Update::Missing) {
+        // If there is no condition clause and no update clause, mark
+        // the loop-ending "goto" with the location of the "for".
+        // This ensures that the debugger will stop on each loop
+        // iteration.
+        if (forPos) {
+            if (!bce_->updateSourceCoordNotes(*forPos))
+                return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Cond;
+#endif
+    return true;
+}
+
+bool
+CForEmitter::emitEnd()
+{
+    MOZ_ASSERT(state_ == State::Cond);
+    // Set the first note offset so we can find the loop condition.
+    if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::For::CondOffset,
+                                condOffset_ - biasedTop_))
+    {
+        return false;
+    }
+    if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::For::UpdateOffset,
+                                loopInfo_->continueTargetOffset() - biasedTop_))
+    {
+        return false;
+    }
+
+    // If no loop condition, just emit a loop-closing jump.
+    if (!loopInfo_->emitLoopEnd(bce_, cond_ == Cond::Present ? JSOP_IFNE : JSOP_GOTO))
+        return false;                                 //
+
+    // The third note offset helps us find the loop-closing jump.
+    if (!bce_->setSrcNoteOffset(noteIndex_, SrcNote::For::BackJumpOffset,
+                                loopInfo_->loopEndOffset() - biasedTop_))
+
+    {
+        return false;
+    }
+
+    if (!bce_->tryNoteList.append(JSTRY_LOOP, bce_->stackDepth, loopInfo_->headOffset(),
+                                  loopInfo_->breakTargetOffset()))
+    {
+        return false;
+    }
+
+    if (!loopInfo_->patchBreaksAndContinues(bce_))
+        return false;
+
+    loopInfo_.reset();
+
+#ifdef DEBUG
+    state_ = State::End;
+#endif
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CForEmitter.h
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_CForEmitter_h
+#define frontend_CForEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "frontend/BytecodeControlStructures.h"
+#include "frontend/TDZCheckCache.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+class EmitterScope;
+
+// Class for emitting bytecode for c-style for block.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `for (init; cond; update) body`
+//     CForEmitter cfor(this, headLexicalEmitterScopeForLet or nullptr);
+//     cfor.emitInit(Some(offset_of_init));
+//     emit(init); // without pushing value
+//     cfor.emitBody(CForEmitter::Cond::Present, Some(offset_of_body));
+//     emit(body);
+//     cfor.emitUpdate(CForEmitter::Update::Present, Some(offset_of_update)));
+//     emit(update);
+//     cfor.emitCond(Some(offset_of_for),
+//                   Some(offset_of_cond),
+//                   Some(offset_of_end));
+//     emit(cond);
+//     cfor.emitEnd();
+//
+//   `for (;;) body`
+//     CForEmitter cfor(this, nullptr);
+//     cfor.emitInit(Nothing());
+//     cfor.emitBody(CForEmitter::Cond::Missing, Some(offset_of_body));
+//     emit(body);
+//     cfor.emitUpdate(CForEmitter::Update::Missing, Nothing());
+//     cfor.emitCond(Some(offset_of_for),
+//                   Nothing(),
+//                   Some(offset_of_end));
+//     cfor.emitEnd();
+//
+class MOZ_STACK_CLASS CForEmitter
+{
+    // Basic structure of the bytecode (not complete).
+    //
+    // If `cond` is not empty:
+    //     {init}
+    //     JSOP_GOTO entry
+    //   loop:
+    //     {body}
+    //     {update}
+    //   entry:
+    //     {cond}
+    //     JSOP_IFNE loop
+    //
+    // If `cond` is empty:
+    //     {init}
+    //   loop:
+    //     {body}
+    //     {update}
+    //     JSOP_GOTO loop
+    //
+  public:
+    enum class Cond {
+        Missing,
+        Present
+    };
+    enum class Update {
+        Missing,
+        Present
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    // The source note index for SRC_FOR.
+    unsigned noteIndex_ = 0;
+
+    // The bytecode offset of loop condition.
+    // Not the bytecode offset of loop condition expression itself.
+    ptrdiff_t condOffset_ = 0;
+
+    // The base bytecode offset used by SRC_FOR.
+    ptrdiff_t biasedTop_ = 0;
+
+    // Whether the c-style for loop has `cond` and `update`.
+    Cond cond_ = Cond::Missing;
+    Update update_ = Update::Missing;
+
+    mozilla::Maybe<LoopControl> loopInfo_;
+
+    // The lexical scope to be freshened for each iteration.
+    // See the comment in `emitBody` for more details.
+    //
+    // ### Scope freshening
+    //
+    // Each iteration of a `for (let V...)` loop creates a fresh loop variable
+    // binding for V, even if the loop is a C-style `for(;;)` loop:
+    //
+    //     var funcs = [];
+    //     for (let i = 0; i < 2; i++)
+    //         funcs.push(function() { return i; });
+    //     assertEq(funcs[0](), 0);  // the two closures capture...
+    //     assertEq(funcs[1](), 1);  // ...two different `i` bindings
+    //
+    // This is implemented by "freshening" the implicit block -- changing the
+    // scope chain to a fresh clone of the instantaneous block object -- each
+    // iteration, just before evaluating the "update" in for(;;) loops.
+    //
+    // ECMAScript doesn't freshen in `for (const ...;;)`.  Lack of freshening
+    // isn't directly observable in-language because `const`s can't be mutated,
+    // but it *can* be observed in the Debugger API.
+    const EmitterScope* headLexicalEmitterScopeForLet_;
+
+    mozilla::Maybe<TDZCheckCache> tdzCache_;
+
+#ifdef DEBUG
+    // The state of this emitter.
+    //
+    // +-------+ emitInit +------+ emitBody +------+ emitUpdate +--------+
+    // | Start |--------->| Init |--------->| Body |----------->| Update |-+
+    // +-------+          +------+          +------+            +--------+ |
+    //                                                                     |
+    //                                   +---------------------------------+
+    //                                   |
+    //                                   | emitCond +------+ emitEnd +-----+
+    //                                   +--------->| Cond |-------->| End |
+    //                                              +------+         +-----+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling emitInit.
+        Init,
+
+        // After calling emitBody.
+        Body,
+
+        // After calling emitUpdate.
+        Update,
+
+        // After calling emitCond.
+        Cond,
+
+        // After calling emitEnd.
+        End
+    };
+    State state_ = State::Start;
+#endif
+
+  public:
+    CForEmitter(BytecodeEmitter* bce, const EmitterScope* headLexicalEmitterScopeForLet);
+
+    // Parameters are the offset in the source code for each character below:
+    //
+    //   for ( x = 10 ; x < 20 ; x ++ ) { f(x); }
+    //   ^     ^        ^        ^      ^       ^
+    //   |     |        |        |      |       |
+    //   |     |        |        |      |       endPos
+    //   |     |        |        |      |
+    //   |     |        |        |      bodyPos
+    //   |     |        |        |
+    //   |     |        |        updatePos
+    //   |     |        |
+    //   |     |        condPos
+    //   |     |
+    //   |     initPos
+    //   |
+    //   forPos
+    //
+    // Can be Nothing() if not available.
+    MOZ_MUST_USE bool emitInit(const mozilla::Maybe<uint32_t>& initPos);
+    MOZ_MUST_USE bool emitBody(Cond cond, const mozilla::Maybe<uint32_t>& bodyPos);
+    MOZ_MUST_USE bool emitUpdate(Update update, const mozilla::Maybe<uint32_t>& updatePos);
+    MOZ_MUST_USE bool emitCond(const mozilla::Maybe<uint32_t>& forPos,
+                               const mozilla::Maybe<uint32_t>& condPos,
+                               const mozilla::Maybe<uint32_t>& endPos);
+    MOZ_MUST_USE bool emitEnd();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CForEmitter_h */
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -361,17 +361,17 @@ EmitterScope::appendScopeNote(BytecodeEm
                "Scope notes are not needed for body-level scopes.");
     noteIndex_ = bce->scopeNoteList.length();
     return bce->scopeNoteList.append(index(), bce->offset(), bce->inPrologue(),
                                      enclosingInFrame() ? enclosingInFrame()->noteIndex()
                                                         : ScopeNote::NoScopeNoteIndex);
 }
 
 bool
-EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd)
+EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart, uint32_t slotEnd) const
 {
     // Lexical bindings throw ReferenceErrors if they are used before
     // initialization. See ES6 8.1.1.1.6.
     //
     // For completeness, lexical bindings are initialized in ES6 by calling
     // InitializeBinding, after which touching the binding will no longer
     // throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
     // 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
@@ -952,17 +952,17 @@ EmitterScope::enterWith(BytecodeEmitter*
 
     if (!appendScopeNote(bce))
         return false;
 
     return checkEnvironmentChainLength(bce);
 }
 
 bool
-EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce)
+EmitterScope::deadZoneFrameSlots(BytecodeEmitter* bce) const
 {
     return deadZoneFrameSlotRange(bce, frameSlotStart(), frameSlotEnd());
 }
 
 bool
 EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
 {
     // If we aren't leaving the scope due to a non-local jump (e.g., break),
--- a/js/src/frontend/EmitterScope.h
+++ b/js/src/frontend/EmitterScope.h
@@ -82,34 +82,34 @@ class EmitterScope : public Nestable<Emi
 
     template <typename ScopeCreator>
     MOZ_MUST_USE bool internScope(BytecodeEmitter* bce, ScopeCreator createScope);
     template <typename ScopeCreator>
     MOZ_MUST_USE bool internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope);
     MOZ_MUST_USE bool appendScopeNote(BytecodeEmitter* bce);
 
     MOZ_MUST_USE bool deadZoneFrameSlotRange(BytecodeEmitter* bce, uint32_t slotStart,
-                                             uint32_t slotEnd);
+                                             uint32_t slotEnd) const;
 
   public:
     explicit EmitterScope(BytecodeEmitter* bce);
 
     void dump(BytecodeEmitter* bce);
 
     MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
                                    Handle<LexicalScope::Data*> bindings);
     MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
     MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
     MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce, FunctionBox* funbox);
     MOZ_MUST_USE bool enterParameterExpressionVar(BytecodeEmitter* bce);
     MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce, GlobalSharedContext* globalsc);
     MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
     MOZ_MUST_USE bool enterModule(BytecodeEmitter* module, ModuleSharedContext* modulesc);
     MOZ_MUST_USE bool enterWith(BytecodeEmitter* bce);
-    MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce);
+    MOZ_MUST_USE bool deadZoneFrameSlots(BytecodeEmitter* bce) const;
 
     MOZ_MUST_USE bool leave(BytecodeEmitter* bce, bool nonLocal = false);
 
     uint32_t index() const {
         MOZ_ASSERT(scopeIndex_ != ScopeNote::NoScopeIndex, "Did you forget to intern a Scope?");
         return scopeIndex_;
     }
 
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -32,16 +32,33 @@ namespace js {
  * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
  *
  * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
  * enum, so its initializers need to match the order here.
  */
 
 class SrcNote {
   public:
+    // SRC_FOR: Source note for JSOP_NOP at the top of C-style for loop,
+    //          which is placed after init expression/declaration ops.
+    class For {
+      public:
+        enum Fields {
+            // The offset of the condition expression ops from JSOP_NOP.
+            CondOffset,
+
+            // The offset of the update expression ops from JSOP_NOP.
+            UpdateOffset,
+
+            // The offset of JSOP_GOTO/JSOP_IFNE at the end of the loop from
+            // JSOP_NOP.
+            BackJumpOffset,
+            Count,
+        };
+    };
     // SRC_TABLESWITCH: Source note for JSOP_TABLESWITCH.
     class TableSwitch {
       public:
         enum Fields {
             // The offset of the end of switch (the first non-JumpTarget op
             // after switch) from JSOP_TABLESWITCH.
             EndOffset,
             Count
@@ -101,17 +118,17 @@ class SrcNote {
     };
 };
 
 #define FOR_EACH_SRC_NOTE_TYPE(M)                                                                  \
     M(SRC_NULL,         "null",        0)  /* Terminates a note vector. */                         \
     M(SRC_IF,           "if",          0)  /* JSOP_IFEQ bytecode is from an if-then. */            \
     M(SRC_IF_ELSE,      "if-else",     0)  /* JSOP_IFEQ bytecode is from an if-then-else. */       \
     M(SRC_COND,         "cond",        0)  /* JSOP_IFEQ is from conditional ?: operator. */        \
-    M(SRC_FOR,          "for",         3)  /* JSOP_NOP or JSOP_POP in for(;;) loop head. */        \
+    M(SRC_FOR,          "for",         SrcNote::For::Count) \
     M(SRC_WHILE,        "while",       1)  /* JSOP_GOTO to for or while loop condition from before \
                                               loop, else JSOP_NOP at top of do-while loop. */      \
     M(SRC_FOR_IN,       "for-in",      1)  /* JSOP_GOTO to for-in loop condition from before       \
                                               loop. */                                             \
     M(SRC_FOR_OF,       "for-of",      1)  /* JSOP_GOTO to for-of loop condition from before       \
                                               loop. */                                             \
     M(SRC_CONTINUE,     "continue",    0)  /* JSOP_GOTO is a continue. */                          \
     M(SRC_BREAK,        "break",       0)  /* JSOP_GOTO is a break. */                             \
--- a/js/src/jit/IonControlFlow.cpp
+++ b/js/src/jit/IonControlFlow.cpp
@@ -1429,60 +1429,61 @@ ControlFlowGenerator::maybeLoop(JSOp op,
 
 ControlFlowGenerator::ControlStatus
 ControlFlowGenerator::processForLoop(JSOp op, jssrcnote* sn)
 {
     // Skip the NOP.
     MOZ_ASSERT(op == JSOP_NOP);
     pc = GetNextPc(pc);
 
-    jsbytecode* condpc = pc + GetSrcNoteOffset(sn, 0);
-    jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, 1);
-    jsbytecode* ifne = pc + GetSrcNoteOffset(sn, 2);
-    jsbytecode* exitpc = GetNextPc(ifne);
+    jsbytecode* condpc = pc + GetSrcNoteOffset(sn, SrcNote::For::CondOffset);
+    jsbytecode* updatepc = pc + GetSrcNoteOffset(sn, SrcNote::For::UpdateOffset);
+    jsbytecode* backjumppc = pc + GetSrcNoteOffset(sn, SrcNote::For::BackJumpOffset);
+    jsbytecode* exitpc = GetNextPc(backjumppc);
 
     // for loops have the following structures:
     //
-    //   NOP or POP
-    //   [GOTO cond | NOP]
+    //   NOP
+    //   [GOTO cond]
     //   LOOPHEAD
     // body:
     //    ; [body]
-    // [increment:]
+    // [update:]
     //   [FRESHENBLOCKSCOPE, if needed by a cloned block]
-    //    ; [increment]
+    //    ; [update]
     // [cond:]
     //   LOOPENTRY
-    //   GOTO body
+    //    ; [cond]
+    //   [GOTO body | IFNE body]
     //
-    // If there is a condition (condpc != ifne), this acts similar to a while
+    // If there is a condition (condpc != backjumppc), this acts similar to a while
     // loop otherwise, it acts like a do-while loop.
     //
     // Note that currently Ion does not compile pushblockscope/popblockscope as
     // necessary prerequisites to freshenblockscope.  So the code below doesn't
     // and needn't consider the implications of freshenblockscope.
     jsbytecode* bodyStart = pc;
     jsbytecode* bodyEnd = updatepc;
     jsbytecode* loopEntry = condpc;
-    if (condpc != ifne) {
+    if (condpc != backjumppc) {
         MOZ_ASSERT(JSOp(*bodyStart) == JSOP_GOTO);
         MOZ_ASSERT(bodyStart + GetJumpOffset(bodyStart) == condpc);
         bodyStart = GetNextPc(bodyStart);
     } else {
         // No loop condition, such as for(j = 0; ; j++)
         if (op != JSOP_NOP) {
             // If the loop starts with POP, we have to skip a NOP.
             MOZ_ASSERT(JSOp(*bodyStart) == JSOP_NOP);
             bodyStart = GetNextPc(bodyStart);
         }
         loopEntry = GetNextPc(bodyStart);
     }
     jsbytecode* loopHead = bodyStart;
     MOZ_ASSERT(JSOp(*bodyStart) == JSOP_LOOPHEAD);
-    MOZ_ASSERT(ifne + GetJumpOffset(ifne) == bodyStart);
+    MOZ_ASSERT(backjumppc + GetJumpOffset(backjumppc) == bodyStart);
     bodyStart = GetNextPc(bodyStart);
 
     MOZ_ASSERT(JSOp(*loopEntry) == JSOP_LOOPENTRY);
 
     CFGBlock* header = CFGBlock::New(alloc(), loopEntry);
 
     CFGLoopEntry* ins = CFGLoopEntry::New(alloc(), header, 0);
     if (LoopEntryCanIonOsr(loopEntry))
@@ -1490,34 +1491,34 @@ ControlFlowGenerator::processForLoop(JSO
 
     current->setStopIns(ins);
     current->setStopPc(pc);
 
     // If there is no condition, we immediately parse the body. Otherwise, we
     // parse the condition.
     jsbytecode* stopAt;
     CFGState::State initial;
-    if (condpc != ifne) {
+    if (condpc != backjumppc) {
         pc = condpc;
-        stopAt = ifne;
+        stopAt = backjumppc;
         initial = CFGState::FOR_LOOP_COND;
     } else {
         pc = bodyStart;
         stopAt = bodyEnd;
         initial = CFGState::FOR_LOOP_BODY;
     }
 
     if (!pushLoop(initial, stopAt, current,
                   loopHead, pc, bodyStart, bodyEnd, exitpc, updatepc))
     {
         return ControlStatus::Error;
     }
 
     CFGState& state = cfgStack_.back();
-    state.loop.condpc = (condpc != ifne) ? condpc : nullptr;
+    state.loop.condpc = (condpc != backjumppc) ? condpc : nullptr;
     state.loop.updatepc = (updatepc != condpc) ? updatepc : nullptr;
     if (state.loop.updatepc)
         state.loop.updateEnd = condpc;
 
     current = header;
     if (!addBlock(current))
         return ControlStatus::Error;
     return ControlStatus::Jumped;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -206,16 +206,17 @@ UNIFIED_SOURCES += [
     'builtin/WeakSetObject.cpp',
     'devtools/sharkctl.cpp',
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeControlStructures.cpp',
     'frontend/BytecodeEmitter.cpp',
+    'frontend/CForEmitter.cpp',
     'frontend/EmitterScope.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/ForOfLoopControl.cpp',
     'frontend/IfEmitter.cpp',
     'frontend/JumpList.cpp',
     'frontend/NameFunctions.cpp',
     'frontend/ParseNode.cpp',
     'frontend/SwitchEmitter.cpp',
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -2738,20 +2738,20 @@ SrcNotes(JSContext* cx, HandleScript scr
                 return false;
             break;
 
           case SRC_NEWLINE:
             ++lineno;
             break;
 
           case SRC_FOR:
-            if (!sp->jsprintf(" cond %u update %u tail %u",
-                              unsigned(GetSrcNoteOffset(sn, 0)),
-                              unsigned(GetSrcNoteOffset(sn, 1)),
-                              unsigned(GetSrcNoteOffset(sn, 2))))
+            if (!sp->jsprintf(" cond %u update %u backjump %u",
+                              unsigned(GetSrcNoteOffset(sn, SrcNote::For::CondOffset)),
+                              unsigned(GetSrcNoteOffset(sn, SrcNote::For::UpdateOffset)),
+                              unsigned(GetSrcNoteOffset(sn, SrcNote::For::BackJumpOffset))))
             {
                 return false;
             }
             break;
 
           case SRC_FOR_IN:
           case SRC_FOR_OF:
             if (!sp->jsprintf(" closingjump %u", unsigned(GetSrcNoteOffset(sn, 0))))