Bug 1227677 - Emit code for PNK_COMPREHENSIONFOR using separate code from that used for for-loops. r=shu
authorJeff Walden <jwalden@mit.edu>
Tue, 24 Nov 2015 11:00:44 -0800
changeset 309916 e17be37dbd32eefe47f41395aa89d7353dec4b55
parent 309915 748c27d6775f1b3b62e2029a0d750843ebfc355d
child 309917 781b2dc8f21621fed3f5cadbaebcb110e5736369
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs1227677
milestone45.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 1227677 - Emit code for PNK_COMPREHENSIONFOR using separate code from that used for for-loops. r=shu
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -5876,29 +5876,340 @@ BytecodeEmitter::emitCStyleFor(ParseNode
     /* Now fixup all breaks and continues. */
     popStatement();
     return true;
 }
 
 bool
 BytecodeEmitter::emitFor(ParseNode* pn)
 {
+    MOZ_ASSERT(pn->isKind(PNK_FOR));
+
     if (pn->pn_left->isKind(PNK_FORHEAD))
         return emitCStyleFor(pn);
 
     if (!updateLineNumberNotes(pn->pn_pos.begin))
         return false;
 
     if (pn->pn_left->isKind(PNK_FORIN))
         return emitForIn(pn);
 
     MOZ_ASSERT(pn->pn_left->isKind(PNK_FOROF));
     return emitForOf(StmtType::FOR_OF_LOOP, pn);
 }
 
+bool
+BytecodeEmitter::emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letDecl)
+{
+    // ES6 specifies that loop variables get a fresh binding in each iteration.
+    // This is currently implemented for C-style for(;;) loops, but not
+    // for-in/of loops, though a similar approach should work. See bug 449811.
+    //
+    // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a
+    // scope containing an uninitialized `x`. If EXPR accesses `x`, we should
+    // get a ReferenceError due to the TDZ violation. This is not yet
+    // implemented. See bug 1069480.
+
+    *letDecl = pn->isKind(PNK_LEXICALSCOPE);
+    MOZ_ASSERT_IF(*letDecl, pn->isLexical());
+
+    // If the left part is 'var x', emit code to define x if necessary using a
+    // prologue opcode, but do not emit a pop. If it is 'let x', enterBlockScope
+    // will initialize let bindings in emitForOf and emitForIn with
+    // undefineds.
+    //
+    // Due to the horror of legacy comprehensions, there is a third case where
+    // we have PNK_LET without a lexical scope, because those expressions are
+    // parsed with single lexical scope for the entire comprehension. In this
+    // case we must initialize the lets to not trigger dead zone checks via
+    // InitializeVars.
+    if (!*letDecl) {
+        emittingForInit = true;
+        if (pn->isKind(PNK_VAR)) {
+            if (!emitVariables(pn, DefineVars))
+                return false;
+        } else {
+            MOZ_ASSERT(pn->isKind(PNK_LET));
+            if (!emitVariables(pn, InitializeVars))
+                return false;
+        }
+        emittingForInit = false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitComprehensionForOf(ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR));
+
+    ParseNode* forHead = pn ? pn->pn_left : nullptr;
+    MOZ_ASSERT(forHead->isKind(PNK_FOROF));
+
+    ParseNode* forHeadExpr = forHead ? forHead->pn_kid3 : nullptr;
+
+    ParseNode* forBody = pn ? pn->pn_right : nullptr;
+
+    ptrdiff_t top = offset();
+
+    ParseNode* loopDecl = forHead ? forHead->pn_kid1 : nullptr;
+    bool letDecl = false;
+    if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &letDecl))
+        return false;
+
+    // For-of loops run with two values on the stack: the iterator and the
+    // current result object.
+
+    // Compile the object expression to the right of 'of'.
+    if (!emitTree(forHeadExpr))                // EXPR
+        return false;
+    if (!emitIterator())                       // ITER
+        return false;
+
+    // Push a dummy result so that we properly enter iteration midstream.
+    if (!emit1(JSOP_UNDEFINED))                // ITER RESULT
+        return false;
+
+    // Enter the block before the loop body, after evaluating the obj.
+    // Initialize let bindings with undefined when entering, as the name
+    // assigned to is a plain assignment.
+    StmtInfoBCE letStmt(cx);
+    if (letDecl) {
+        if (!enterBlockScope(&letStmt, loopDecl->pn_objbox, JSOP_UNDEFINED, 0))
+            return false;
+    }
+
+    LoopStmtInfo stmtInfo(cx);
+    pushLoopStatement(&stmtInfo, StmtType::FOR_OF_LOOP, top);
+
+    // Jump down to the loop condition to minimize overhead assuming at least
+    // one iteration, as the other loop forms do.  Annotate so IonMonkey can
+    // find the loop-closing jump.
+    unsigned noteIndex;
+    if (!newSrcNote(SRC_FOR_OF, &noteIndex))
+        return false;
+    ptrdiff_t jmp;
+    if (!emitJump(JSOP_GOTO, 0, &jmp))
+        return false;
+
+    top = offset();
+    stmtInfo.setTop(top);
+    if (!emitLoopHead(nullptr))
+        return false;
+
+#ifdef DEBUG
+    int loopDepth = this->stackDepth;
+#endif
+
+    // Emit code to assign result.value to the iteration variable.
+    if (!emit1(JSOP_DUP))                                 // ITER RESULT RESULT
+        return false;
+    if (!emitAtomOp(cx->names().value, JSOP_GETPROP))     // ITER RESULT VALUE
+        return false;
+    if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr)) // ITER RESULT VALUE
+        return false;
+    if (!emit1(JSOP_POP))                                 // ITER RESULT
+        return false;
+
+    // The stack should be balanced around the assignment opcode sequence.
+    MOZ_ASSERT(this->stackDepth == loopDepth);
+
+    // Emit code for the loop body.
+    if (!emitTree(forBody))
+        return false;
+
+    // Set loop and enclosing "update" offsets, for continue.
+    StmtInfoBCE* stmt = &stmtInfo;
+    do {
+        stmt->update = offset();
+    } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL);
+
+    // COME FROM the beginning of the loop to here.
+    setJumpOffsetAt(jmp);
+    if (!emitLoopEntry(forHeadExpr))
+        return false;
+
+    if (!emit1(JSOP_POP))                                 // ITER
+        return false;
+    if (!emit1(JSOP_DUP))                                 // ITER ITER
+        return false;
+    if (!emitIteratorNext(forHead))                       // ITER RESULT
+        return false;
+    if (!emit1(JSOP_DUP))                                 // ITER RESULT RESULT
+        return false;
+    if (!emitAtomOp(cx->names().done, JSOP_GETPROP))      // ITER RESULT DONE?
+        return false;
+
+    ptrdiff_t beq;
+    if (!emitJump(JSOP_IFEQ, top - offset(), &beq))       // ITER RESULT
+        return false;
+
+    MOZ_ASSERT(this->stackDepth == loopDepth);
+
+    // Let Ion know where the closing jump of this loop is.
+    if (!setSrcNoteOffset(noteIndex, 0, beq - jmp))
+        return false;
+
+    // Fixup breaks and continues.
+    popStatement();
+
+    if (!tryNoteList.append(JSTRY_FOR_OF, stackDepth, top, offset()))
+        return false;
+
+    if (letDecl) {
+        if (!leaveNestedScope(&letStmt))
+            return false;
+    }
+
+    // Pop the result and the iter.
+    return emitUint16Operand(JSOP_POPN, 2);               //
+}
+
+bool
+BytecodeEmitter::emitComprehensionForIn(ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isKind(PNK_COMPREHENSIONFOR));
+
+    ptrdiff_t top = offset();
+
+    ParseNode* forHead = pn->pn_left;
+    MOZ_ASSERT(forHead->isKind(PNK_FORIN));
+
+    ParseNode* forBody = pn->pn_right;
+
+    ParseNode* loopDecl = forHead->pn_kid1;
+    bool letDecl = false;
+    if (loopDecl && !emitComprehensionForInOrOfVariables(loopDecl, &letDecl))
+        return false;
+
+    /* Compile the object expression to the right of 'in'. */
+    if (!emitTree(forHead->pn_kid3))
+        return 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).
+     */
+    MOZ_ASSERT(pn->isOp(JSOP_ITER));
+    if (!emit2(JSOP_ITER, (uint8_t) pn->pn_iflags))
+        return false;
+
+    // For-in loops have both the iterator and the value on the stack. Push
+    // undefined to balance the stack.
+    if (!emit1(JSOP_UNDEFINED))
+        return false;
+
+    // Enter the block before the loop body, after evaluating the obj.
+    // Initialize let bindings with undefined when entering, as the name
+    // assigned to is a plain assignment.
+    StmtInfoBCE letStmt(cx);
+    if (letDecl) {
+        if (!enterBlockScope(&letStmt, loopDecl->pn_objbox, JSOP_UNDEFINED, 0))
+            return false;
+    }
+
+    LoopStmtInfo stmtInfo(cx);
+    pushLoopStatement(&stmtInfo, StmtType::FOR_IN_LOOP, top);
+
+    /* Annotate so IonMonkey can find the loop-closing jump. */
+    unsigned noteIndex;
+    if (!newSrcNote(SRC_FOR_IN, &noteIndex))
+        return false;
+
+    /*
+     * Jump down to the loop condition to minimize overhead assuming at
+     * least one iteration, as the other loop forms do.
+     */
+    ptrdiff_t jmp;
+    if (!emitJump(JSOP_GOTO, 0, &jmp))
+        return false;
+
+    top = offset();
+    stmtInfo.setTop(top);
+    if (!emitLoopHead(nullptr))
+        return false;
+
+#ifdef DEBUG
+    int loopDepth = this->stackDepth;
+#endif
+
+    // Emit code to assign the enumeration value to the left hand side, but
+    // also leave it on the stack.
+    if (!emitAssignment(forHead->pn_kid2, JSOP_NOP, nullptr))
+        return false;
+
+    /* The stack should be balanced around the assignment opcode sequence. */
+    MOZ_ASSERT(this->stackDepth == loopDepth);
+
+    /* Emit code for the loop body. */
+    if (!emitTree(forBody))
+        return false;
+
+    /* Set loop and enclosing "update" offsets, for continue. */
+    StmtInfoBCE* stmt = &stmtInfo;
+    do {
+        stmt->update = offset();
+    } while ((stmt = stmt->enclosing) != nullptr && stmt->type == StmtType::LABEL);
+
+    /*
+     * Fixup the goto that starts the loop to jump down to JSOP_MOREITER.
+     */
+    setJumpOffsetAt(jmp);
+    if (!emitLoopEntry(nullptr))
+        return false;
+    if (!emit1(JSOP_POP))
+        return false;
+    if (!emit1(JSOP_MOREITER))
+        return false;
+    if (!emit1(JSOP_ISNOITER))
+        return false;
+    ptrdiff_t beq;
+    if (!emitJump(JSOP_IFEQ, top - offset(), &beq))
+        return false;
+
+    /* Set the srcnote offset so we can find the closing jump. */
+    if (!setSrcNoteOffset(noteIndex, 0, beq - jmp))
+        return false;
+
+    // Fix up breaks and continues.
+    popStatement();
+
+    // Pop the enumeration value.
+    if (!emit1(JSOP_POP))
+        return false;
+
+    if (!tryNoteList.append(JSTRY_FOR_IN, this->stackDepth, top, offset()))
+        return false;
+    if (!emit1(JSOP_ENDITER))
+        return false;
+
+    if (letDecl) {
+        if (!leaveNestedScope(&letStmt))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitComprehensionFor(ParseNode* compFor)
+{
+    MOZ_ASSERT(compFor->pn_left->isKind(PNK_FORIN) ||
+               compFor->pn_left->isKind(PNK_FOROF));
+
+    if (!updateLineNumberNotes(compFor->pn_pos.begin))
+        return false;
+
+    return compFor->pn_left->isKind(PNK_FORIN)
+           ? emitComprehensionForIn(compFor)
+           : emitComprehensionForOf(compFor);
+}
+
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
 {
     FunctionBox* funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
 
     /*
@@ -7976,18 +8287,22 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         break;
 
       case PNK_DOWHILE:
         if (!emitDo(pn))
             return false;
         break;
 
       case PNK_FOR:
+        if (!emitFor(pn))
+            return false;
+        break;
+
       case PNK_COMPREHENSIONFOR:
-        if (!emitFor(pn))
+        if (!emitComprehensionFor(pn))
             return false;
         break;
 
       case PNK_BREAK:
         if (!emitBreak(pn->as<BreakStatement>().label()))
             return false;
         break;
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -596,16 +596,21 @@ struct BytecodeEmitter
 
     bool emitConditionalExpression(ConditionalExpression& conditional);
 
     bool emitCallOrNew(ParseNode* pn);
     bool emitSelfHostedCallFunction(ParseNode* pn);
     bool emitSelfHostedResumeGenerator(ParseNode* pn);
     bool emitSelfHostedForceInterpreter(ParseNode* pn);
 
+    bool emitComprehensionFor(ParseNode* compFor);
+    bool emitComprehensionForIn(ParseNode* pn);
+    bool emitComprehensionForInOrOfVariables(ParseNode* pn, bool* letDecl);
+    bool emitComprehensionForOf(ParseNode* pn);
+
     bool emitDo(ParseNode* pn);
     bool emitFor(ParseNode* pn);
     bool emitForIn(ParseNode* pn);
     bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl);
     bool emitCStyleFor(ParseNode* pn);
     bool emitWhile(ParseNode* pn);
 
     bool emitBreak(PropertyName* label);
@@ -622,18 +627,17 @@ struct BytecodeEmitter
     // and the iterator to be on the stack in that order (iterator on the bottom).
     // It will pop the iterator and I, then iterate over the iterator by calling
     // |.next()| and put the results into the I-th element of array with
     // incrementing I, then push the result I (it will be original I +
     // iteration count). The stack after iteration will look like |ARRAY INDEX|.
     bool emitSpread();
 
     // If type is StmtType::FOR_OF_LOOP, emit bytecode for a for-of loop.
-    // pn should be PNK_FOR or PNK_COMPREHENSIONFOR, and pn->pn_left should be
-    // PNK_FOROF.
+    // pn should be PNK_FOR, and pn->pn_left should be PNK_FOROF.
     //
     // If type is StmtType::SPREAD, emit bytecode for spread operator.
     // pn should be nullptr.
     //
     // Please refer the comment above emitSpread for additional information about
     // stack convention.
     bool emitForOf(StmtType type, ParseNode* pn);