Bug 1322069 - Add TryEmitter. r=shu, a=lizzard
authorTooru Fujisawa <arai_a@mac.com>
Sat, 28 Jan 2017 20:54:25 +0900
changeset 379377 0362a10123ca2d81713ecef22da21511f635a80b
parent 379376 b84d600235f33f2091625058a718ea819c35619d
child 379378 f3a27a0d98847ec5bf3e340988d561ff184ddbb2
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu, lizzard
bugs1322069
milestone53.0
Bug 1322069 - Add TryEmitter. r=shu, a=lizzard
js/src/frontend/BytecodeEmitter.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -1564,16 +1564,328 @@ BytecodeEmitter::TDZCheckCache::noteTDZC
     } else {
         if (!cache_->add(p, name, check))
             return false;
     }
 
     return true;
 }
 
+class MOZ_STACK_CLASS TryEmitter
+{
+  public:
+    enum Kind {
+        TryCatch,
+        TryCatchFinally,
+        TryFinally
+    };
+    enum ShouldUseRetVal {
+        UseRetVal,
+        DontUseRetVal
+    };
+    enum ShouldUseControl {
+        UseControl,
+        DontUseControl,
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+    Kind kind_;
+    ShouldUseRetVal retValKind_;
+
+    // Track jumps-over-catches and gosubs-to-finally for later fixup.
+    //
+    // When a finally block is active, non-local jumps (including
+    // jumps-over-catches) result in a GOSUB being written into the bytecode
+    // stream and fixed-up later.
+    //
+    // If ShouldUseControl is DontUseControl, all that handling is skipped.
+    // DontUseControl is used by yield*, that matches to the following:
+    //   * has only one catch block
+    //   * has no catch guard
+    //   * has JSOP_GOTO at the end of catch-block
+    //   * has no non-local-jump
+    //   * doesn't use finally block for normal completion of try-block and
+    //     catch-block
+    Maybe<TryFinallyControl> controlInfo_;
+
+    int depth_;
+    unsigned noteIndex_;
+    ptrdiff_t tryStart_;
+    JumpList catchAndFinallyJump_;
+    JumpTarget tryEnd_;
+    JumpTarget finallyStart_;
+
+    enum State {
+        Start,
+        Try,
+        TryEnd,
+        Catch,
+        CatchEnd,
+        Finally,
+        FinallyEnd,
+        End
+    };
+    State state_;
+
+    bool hasCatch() const {
+        return kind_ == TryCatch || kind_ == TryCatchFinally;
+    }
+    bool hasFinally() const {
+        return kind_ == TryCatchFinally || kind_ == TryFinally;
+    }
+
+  public:
+    TryEmitter(BytecodeEmitter* bce, Kind kind, ShouldUseRetVal retValKind = UseRetVal,
+               ShouldUseControl controlKind = UseControl)
+      : bce_(bce),
+        kind_(kind),
+        retValKind_(retValKind),
+        depth_(0),
+        noteIndex_(0),
+        tryStart_(0),
+        state_(Start)
+    {
+        if (controlKind == UseControl)
+            controlInfo_.emplace(bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
+        finallyStart_.offset = 0;
+    }
+
+    bool emitJumpOverCatchAndFinally() {
+        if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+            return false;
+        return true;
+    }
+
+    bool emitTry() {
+        MOZ_ASSERT(state_ == Start);
+
+        // Since an exception can be thrown at any place inside the try block,
+        // we need to restore the stack and the scope chain before we transfer
+        // the control to the exception handler.
+        //
+        // For that we store in a try note associated with the catch or
+        // finally block the stack depth upon the try entry. The interpreter
+        // uses this depth to properly unwind the stack and the scope chain.
+        depth_ = bce_->stackDepth;
+
+        // Record the try location, then emit the try block.
+        if (!bce_->newSrcNote(SRC_TRY, &noteIndex_))
+            return false;
+        if (!bce_->emit1(JSOP_TRY))
+            return false;
+        tryStart_ = bce_->offset();
+
+        state_ = Try;
+        return true;
+    }
+
+  private:
+    bool emitTryEnd() {
+        MOZ_ASSERT(state_ == Try);
+        MOZ_ASSERT(depth_ == bce_->stackDepth);
+
+        // GOSUB to finally, if present.
+        if (hasFinally() && controlInfo_) {
+            if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
+                return false;
+        }
+
+        // Source note points to the jump at the end of the try block.
+        if (!bce_->setSrcNoteOffset(noteIndex_, 0, bce_->offset() - tryStart_ + JSOP_TRY_LENGTH))
+            return false;
+
+        // Emit jump over catch and/or finally.
+        if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+            return false;
+
+        if (!bce_->emitJumpTarget(&tryEnd_))
+            return false;
+
+        return true;
+    }
+
+  public:
+    bool emitCatch() {
+        if (state_ == Try) {
+            if (!emitTryEnd())
+                return false;
+        } else {
+            MOZ_ASSERT(state_ == Catch);
+            if (!emitCatchEnd(true))
+                return false;
+        }
+
+        MOZ_ASSERT(bce_->stackDepth == depth_);
+
+        if (retValKind_ == UseRetVal) {
+            // Clear the frame's return value that might have been set by the
+            // try block:
+            //
+            //   eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
+            if (!bce_->emit1(JSOP_UNDEFINED))
+                return false;
+            if (!bce_->emit1(JSOP_SETRVAL))
+                return false;
+        }
+
+        state_ = Catch;
+        return true;
+    }
+
+  private:
+    bool emitCatchEnd(bool hasNext) {
+        MOZ_ASSERT(state_ == Catch);
+
+        if (!controlInfo_)
+            return true;
+
+        // gosub <finally>, if required.
+        if (hasFinally()) {
+            if (!bce_->emitJump(JSOP_GOSUB, &controlInfo_->gosubs))
+                return false;
+            MOZ_ASSERT(bce_->stackDepth == depth_);
+        }
+
+        // Jump over the remaining catch blocks.  This will get fixed
+        // up to jump to after catch/finally.
+        if (!bce_->emitJump(JSOP_GOTO, &catchAndFinallyJump_))
+            return false;
+
+        // If this catch block had a guard clause, patch the guard jump to
+        // come here.
+        if (controlInfo_->guardJump.offset != -1) {
+            if (!bce_->emitJumpTargetAndPatch(controlInfo_->guardJump))
+                return false;
+            controlInfo_->guardJump.offset = -1;
+
+            // If this catch block is the last one, rethrow, delegating
+            // execution of any finally block to the exception handler.
+            if (!hasNext) {
+                if (!bce_->emit1(JSOP_EXCEPTION))
+                    return false;
+                if (!bce_->emit1(JSOP_THROW))
+                    return false;
+            }
+        }
+
+        return true;
+    }
+
+  public:
+    bool emitFinally(Maybe<uint32_t> finallyPos = Nothing()) {
+        MOZ_ASSERT(hasFinally());
+
+        if (state_ == Try) {
+            if (!emitTryEnd())
+                return false;
+        } else {
+            MOZ_ASSERT(state_ == Catch);
+            if (!emitCatchEnd(false))
+                return false;
+        }
+
+        MOZ_ASSERT(bce_->stackDepth == depth_);
+
+        if (!bce_->emitJumpTarget(&finallyStart_))
+            return false;
+
+        if (controlInfo_) {
+            // Fix up the gosubs that might have been emitted before non-local
+            // jumps to the finally code.
+            bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
+
+            // Indicate that we're emitting a subroutine body.
+            controlInfo_->setEmittingSubroutine();
+        }
+        if (finallyPos) {
+            if (!bce_->updateSourceCoordNotes(finallyPos.value()))
+                return false;
+        }
+        if (!bce_->emit1(JSOP_FINALLY))
+            return false;
+
+        if (retValKind_ == UseRetVal) {
+            if (!bce_->emit1(JSOP_GETRVAL))
+                return false;
+
+            // Clear the frame's return value to make break/continue return
+            // correct value even if there's no other statement before them:
+            //
+            //   eval("x: try { 1 } finally { break x; }"); // undefined, not 1
+            if (!bce_->emit1(JSOP_UNDEFINED))
+                return false;
+            if (!bce_->emit1(JSOP_SETRVAL))
+                return false;
+        }
+
+        state_ = Finally;
+        return true;
+    }
+
+  private:
+    bool emitFinallyEnd() {
+        MOZ_ASSERT(state_ == Finally);
+
+        if (retValKind_ == UseRetVal) {
+            if (!bce_->emit1(JSOP_SETRVAL))
+                return false;
+        }
+
+        if (!bce_->emit1(JSOP_RETSUB))
+            return false;
+
+        bce_->hasTryFinally = true;
+        return true;
+    }
+
+  public:
+    bool emitEnd() {
+        if (state_ == Catch) {
+            MOZ_ASSERT(!hasFinally());
+            if (!emitCatchEnd(false))
+                return false;
+        } else {
+            MOZ_ASSERT(state_ == Finally);
+            MOZ_ASSERT(hasFinally());
+            if (!emitFinallyEnd())
+                return false;
+        }
+
+        MOZ_ASSERT(bce_->stackDepth == depth_);
+
+        // ReconstructPCStack needs a NOP here to mark the end of the last
+        // catch block.
+        if (!bce_->emit1(JSOP_NOP))
+            return false;
+
+        // Fix up the end-of-try/catch jumps to come here.
+        if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_))
+            return false;
+
+        // Add the try note last, to let post-order give us the right ordering
+        // (first to last for a given nesting level, inner to outer by level).
+        if (hasCatch()) {
+            if (!bce_->tryNoteList.append(JSTRY_CATCH, depth_, tryStart_, tryEnd_.offset))
+                return false;
+        }
+
+        // If we've got a finally, mark try+catch region with additional
+        // trynote to catch exceptions (re)thrown from a catch block or
+        // for the try{}finally{} case.
+        if (hasFinally()) {
+            if (!bce_->tryNoteList.append(JSTRY_FINALLY, depth_, tryStart_, finallyStart_.offset))
+                return false;
+        }
+
+        state_ = End;
+        return true;
+    }
+};
+
 class MOZ_STACK_CLASS IfThenElseEmitter
 {
     BytecodeEmitter* bce_;
     JumpList jumpAroundThen_;
     JumpList jumpsAroundElse_;
     unsigned noteIndex_;
     int32_t thenDepth_;
 #ifdef DEBUG
@@ -6080,67 +6392,38 @@ BytecodeEmitter::emitCatch(ParseNode* pn
     return emitTree(pn->pn_kid3);
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the
 // comment on EmitSwitch.
 MOZ_NEVER_INLINE bool
 BytecodeEmitter::emitTry(ParseNode* pn)
 {
-    // Track jumps-over-catches and gosubs-to-finally for later fixup.
-    //
-    // When a finally block is active, non-local jumps (including
-    // jumps-over-catches) result in a GOSUB being written into the bytecode
-    // stream and fixed-up later.
-    //
-    TryFinallyControl controlInfo(this, pn->pn_kid3 ? StatementKind::Finally : StatementKind::Try);
-
-    // Since an exception can be thrown at any place inside the try block,
-    // we need to restore the stack and the scope chain before we transfer
-    // the control to the exception handler.
-    //
-    // For that we store in a try note associated with the catch or
-    // finally block the stack depth upon the try entry. The interpreter
-    // uses this depth to properly unwind the stack and the scope chain.
-    //
-    int depth = stackDepth;
-
-    // Record the try location, then emit the try block.
-    unsigned noteIndex;
-    if (!newSrcNote(SRC_TRY, &noteIndex))
-        return false;
-    if (!emit1(JSOP_TRY))
-        return false;
-
-    ptrdiff_t tryStart = offset();
+    ParseNode* catchList = pn->pn_kid2;
+    ParseNode* finallyNode = pn->pn_kid3;
+
+    TryEmitter::Kind kind;
+    if (catchList) {
+        if (finallyNode)
+            kind = TryEmitter::TryCatchFinally;
+        else
+            kind = TryEmitter::TryCatch;
+    } else {
+        MOZ_ASSERT(finallyNode);
+        kind = TryEmitter::TryFinally;
+    }
+    TryEmitter tryCatch(this, kind);
+
+    if (!tryCatch.emitTry())
+        return false;
+
     if (!emitTree(pn->pn_kid1))
         return false;
-    MOZ_ASSERT(depth == stackDepth);
-
-    // GOSUB to finally, if present.
-    if (pn->pn_kid3) {
-        if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs))
-            return false;
-    }
-
-    // Source note points to the jump at the end of the try block.
-    if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart + JSOP_TRY_LENGTH))
-        return false;
-
-    // Emit jump over catch and/or finally.
-    JumpList catchJump;
-    if (!emitJump(JSOP_GOTO, &catchJump))
-        return false;
-
-    JumpTarget tryEnd;
-    if (!emitJumpTarget(&tryEnd))
-        return false;
 
     // If this try has a catch block, emit it.
-    ParseNode* catchList = pn->pn_kid2;
     if (catchList) {
         MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
 
         // The emitted code for a catch block looks like:
         //
         // [pushlexicalenv]             only if any local aliased
         // exception
         // if there is a catchguard:
@@ -6160,120 +6443,36 @@ BytecodeEmitter::emitTry(ParseNode* pn)
         // goto <end of catch blocks>   non-local; finally applies
         //
         // If there's no catch block without a catchguard, the last <next catch
         // block> points to rethrow code.  This code will [gosub] to the finally
         // code if appropriate, and is also used for the catch-all trynote for
         // capturing exceptions thrown from catch{} blocks.
         //
         for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) {
-            MOZ_ASSERT(this->stackDepth == depth);
-
-            // Clear the frame's return value that might have been set by the
-            // try block:
-            //
-            //   eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
-            if (!emit1(JSOP_UNDEFINED))
-                return false;
-            if (!emit1(JSOP_SETRVAL))
+            if (!tryCatch.emitCatch())
                 return false;
 
             // Emit the lexical scope and catch body.
             MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE));
             if (!emitTree(pn3))
                 return false;
-
-            // gosub <finally>, if required.
-            if (pn->pn_kid3) {
-                if (!emitJump(JSOP_GOSUB, &controlInfo.gosubs))
-                    return false;
-                MOZ_ASSERT(this->stackDepth == depth);
-            }
-
-            // Jump over the remaining catch blocks.  This will get fixed
-            // up to jump to after catch/finally.
-            if (!emitJump(JSOP_GOTO, &catchJump))
-                return false;
-
-            // If this catch block had a guard clause, patch the guard jump to
-            // come here.
-            if (controlInfo.guardJump.offset != -1) {
-                if (!emitJumpTargetAndPatch(controlInfo.guardJump))
-                    return false;
-                controlInfo.guardJump.offset = -1;
-
-                // If this catch block is the last one, rethrow, delegating
-                // execution of any finally block to the exception handler.
-                if (!pn3->pn_next) {
-                    if (!emit1(JSOP_EXCEPTION))
-                        return false;
-                    if (!emit1(JSOP_THROW))
-                        return false;
-                }
-            }
-        }
-    }
-
-    MOZ_ASSERT(this->stackDepth == depth);
+        }
+    }
 
     // Emit the finally handler, if there is one.
-    JumpTarget finallyStart{ 0 };
-    if (pn->pn_kid3) {
-        if (!emitJumpTarget(&finallyStart))
-            return false;
-
-        // Fix up the gosubs that might have been emitted before non-local
-        // jumps to the finally code.
-        patchJumpsToTarget(controlInfo.gosubs, finallyStart);
-
-        // Indicate that we're emitting a subroutine body.
-        controlInfo.setEmittingSubroutine();
-        if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin))
-            return false;
-        if (!emit1(JSOP_FINALLY))
-            return false;
-        if (!emit1(JSOP_GETRVAL))
-            return false;
-
-        // Clear the frame's return value to make break/continue return
-        // correct value even if there's no other statement before them:
-        //
-        //   eval("x: try { 1 } finally { break x; }");  // undefined, not 1
-        if (!emit1(JSOP_UNDEFINED))
-            return false;
-        if (!emit1(JSOP_SETRVAL))
-            return false;
-
-        if (!emitTree(pn->pn_kid3))
-            return false;
-        if (!emit1(JSOP_SETRVAL))
-            return false;
-        if (!emit1(JSOP_RETSUB))
-            return false;
-        hasTryFinally = true;
-        MOZ_ASSERT(this->stackDepth == depth);
-    }
-
-    // ReconstructPCStack needs a NOP here to mark the end of the last catch block.
-    if (!emit1(JSOP_NOP))
-        return false;
-
-    // Fix up the end-of-try/catch jumps to come here.
-    if (!emitJumpTargetAndPatch(catchJump))
-        return false;
-
-    // Add the try note last, to let post-order give us the right ordering
-    // (first to last for a given nesting level, inner to outer by level).
-    if (catchList && !tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd.offset))
-        return false;
-
-    // If we've got a finally, mark try+catch region with additional
-    // trynote to catch exceptions (re)thrown from a catch block or
-    // for the try{}finally{} case.
-    if (pn->pn_kid3 && !tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart.offset))
+    if (finallyNode) {
+        if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin)))
+            return false;
+
+        if (!emitTree(finallyNode))
+            return false;
+    }
+
+    if (!tryCatch.emitEnd())
         return false;
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitIf(ParseNode* pn)
 {
@@ -8047,48 +8246,38 @@ BytecodeEmitter::emitYieldStar(ParseNode
 
     // Initial send value is undefined.
     if (!emit1(JSOP_UNDEFINED))                                  // ITER RECEIVED
         return false;
 
     int depth = stackDepth;
     MOZ_ASSERT(depth >= 2);
 
-    JumpList send;
-    if (!emitJump(JSOP_GOTO, &send))                             // goto send
-        return false;
-
-    // Try prologue.                                             // ITER RESULT
-    unsigned noteIndex;
-    if (!newSrcNote(SRC_TRY, &noteIndex))
-        return false;
+    TryEmitter tryCatch(this, TryEmitter::TryCatchFinally, TryEmitter::DontUseRetVal,
+                        TryEmitter::DontUseControl);
+    if (!tryCatch.emitJumpOverCatchAndFinally())                 // ITER RESULT
+        return false;
+
     JumpTarget tryStart{ offset() };
-    if (!emit1(JSOP_TRY))                                        // tryStart:
-        return false;
+    if (!tryCatch.emitTry())                                     // ITER RESULT
+        return false;
+
     MOZ_ASSERT(this->stackDepth == depth);
 
     // Load the generator object.
     if (!emitTree(gen))                                          // ITER RESULT GENOBJ
         return false;
 
     // Yield RESULT as-is, without re-boxing.
     if (!emitYieldOp(JSOP_YIELD))                                // ITER RECEIVED
         return false;
 
-    // Try epilogue.
-    if (!setSrcNoteOffset(noteIndex, 0, offset() - tryStart.offset))
-        return false;
-    if (!emitJump(JSOP_GOTO, &send))                             // goto send
-        return false;
-
-    JumpTarget tryEnd;
-    if (!emitJumpTarget(&tryEnd))                                // tryEnd:
-        return false;
-
-    // Catch location.
+    if (!tryCatch.emitCatch())                                   // ITER RESULT
+        return false;
+
     stackDepth = uint32_t(depth);                                // ITER RESULT
     if (!emit1(JSOP_EXCEPTION))                                  // ITER RESULT EXCEPTION
         return false;
     if (!emitDupAt(2))                                           // ITER RESULT EXCEPTION ITER
         return false;
     if (!emit1(JSOP_DUP))                                        // ITER RESULT EXCEPTION ITER ITER
         return false;
     if (!emitAtomOp(cx->names().throw_, JSOP_CALLPROP))          // ITER RESULT EXCEPTION ITER THROW
@@ -8125,46 +8314,29 @@ BytecodeEmitter::emitYieldStar(ParseNode
     MOZ_ASSERT(this->stackDepth == depth);
     JumpList checkResult;
     // Note that there is no GOSUB to the finally block here. If the iterator has a
     // "throw" method, it does not perform IteratorClose per
     // ES 14.4.13, YieldExpression : yield * AssignmentExpression, step 5.b.ii.
     if (!emitJump(JSOP_GOTO, &checkResult))                      // goto checkResult
         return false;
 
-    // The finally block, IteratorClose logic.
-
-    JumpTarget finallyStart{ 0 };
-    if (!emitJumpTarget(&finallyStart))
-        return false;
-    if (!emit1(JSOP_FINALLY))                                    // ITER RESULT FTYPE FVALUE
-        return false;
+    // IteratorClose logic.
+    if (!tryCatch.emitFinally())
+         return false;
+
     if (!emitDupAt(3))                                           // ITER RESULT FTYPE FVALUE ITER
         return false;
     if (!emitIteratorClose(Some(tryStart)))                      // ITER RESULT FTYPE FVALUE
         return false;
-    if (!emit1(JSOP_RETSUB))                                     // ITER RESULT
-        return false;
-
-    // Catch and finally epilogue.
-
-    // This is a peace offering to ReconstructPCStack.  See the note in EmitTry.
-    if (!emit1(JSOP_NOP))
-        return false;
-    size_t tryStartOffset = tryStart.offset + JSOP_TRY_LENGTH;
-    if (!tryNoteList.append(JSTRY_CATCH, depth, tryStartOffset, tryEnd.offset))
-        return false;
-    if (!tryNoteList.append(JSTRY_FINALLY, depth, tryStartOffset, finallyStart.offset))
+
+    if (!tryCatch.emitEnd())
         return false;
 
     // After the try-catch-finally block: send the received value to the iterator.
-    if (!emitJumpTargetAndPatch(send))                           // send:
-        return false;
-
-    // Send location.
     // result = iter.next(received)                              // ITER RECEIVED
     if (!emit1(JSOP_SWAP))                                       // RECEIVED ITER
         return false;
     if (!emit1(JSOP_DUP))                                        // RECEIVED ITER ITER
         return false;
     if (!emit1(JSOP_DUP))                                        // RECEIVED ITER ITER ITER
         return false;
     if (!emitAtomOp(cx->names().next, JSOP_CALLPROP))            // RECEIVED ITER ITER NEXT