De-duplicate finally blocks.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 22 Dec 2016 22:48:32 +0000 (22:48 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 22 Dec 2016 22:48:32 +0000 (22:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=160168

Reviewed by Saam Barati.

JSTests:

Re-landing r209952 with a few new tests added in test-finally.js.

* stress/deeply-nested-finallys.js: Copied from JSTests/stress/deeply-nested-finallys.js.
- Tests many levels of finally nesting.  This causes the old code to hang (and
  crashes eventually) while trying to generate bytecode for the exponentially
  duplicated finally blocks.  The new code completes this test almost instantly.

* stress/test-finally.js: Copied from JSTests/stress/test-finally.js.
- Tests control flow through various permutations of finally blocks.

Source/JavaScriptCore:

JS execution can arrive at a finally block when there are abrupt completions from
its try or catch block.  The abrupt completion types include Break,
Continue, Return, and Throw.  The non-abrupt completion type is called Normal
(i.e. the case of a try block falling through to the finally block).

Previously, we enable each of these paths for abrupt completion (except for Throw)
to run the finally block code by duplicating the finally block code at each of
the sites that trigger those completions.  This patch fixes the implementation so
that each of these abrupt completions will set a completionTypeRegister (plus a
completionValueRegister for CompletionType::Return) and then jump to the
relevant finally blocks, and continue to thread through subsequent outer finally
blocks until execution reaches the outermost finally block that the completion
type dictates.  We no longer duplicate the finally block code.

The implementation details:
1. We allocate a pair of registers (completionTypeRegister and completionValueRegister)
   just before entering the outermost try-catch-finally scope.

   On allocating the registers, we initialize the completionTypeRegister to
   CompletionType::Normal, and set the completionValueRegister to the empty
   JSValue.

2. The completionTypeRegister will hold a CompletionType value.  This is how we
   encode the CompletionType value to be set:

   a. For Normal, Return, and Throw completion types:
      - The completionTypeRegister is set to CompletionType::Normal,
        CompletionType::Return, and CompletionType::Throw respectively.

   b. For Break and Continue completion types:
      - The completionTypeRegister is set to a unique jumpID where the jumpID is
        computed as:

        jumpID = CompletionType::NumberOfTypes + bytecodeOffset

        The bytecodeOffset used here is the bytecodeOffset of the break or continue
        statement that triggered this completion.

3. Each finally block will have 2 entries:
   a. the catch entry.
   b. the normal entry.

   The catch entry is recorded in the codeBlock's exception handler table,
   and can only be jumped to by the VM's exception handling mechanism.

   The normal entry is recorded in a FinallyContext (at bytecode generation time
   only) and is jumped to when we want enter the finally block due any of the
   other CompletionTypes.

4. How each completion type works?

   CompletionType::Normal
   ======================
   We normally encounter this when falling through from a try or catch block to
   the finally block.

   For the try block case, since completionTypeRegister is set to Normal by default,
   there's nothing more that needs to be done.

   For the catch block case, since we entered the catch block with an exception,
   completionTypeRegister may be set to Throw.  We'll need to set it to Normal
   before jumping to the finally block's normal entry.

   CompletionType::Break
   =====================
   When we emit bytecode for the BreakNode, we check if we have any FinallyContexts
   that we need to service before jumping to the breakTarget.  If we don't, then
   emit op_jump to the breakTarget as usual.  Otherwise:

   a. we'll register a jumpID and the breakTarget with the FinallyContext for the
      outermost finally block that we're supposed to run through.
   b. we'll also increment the numberOfBreaksOrContinues count in each FinallyContext
      from the innermost to the one for that outermost finally block.
   c. emit bytecode to set the completionTypeRegister to the jumpID.
   d. emit bytecode to jump to the normal entry of the innermost finally block.

   Each finally block will take care of cascading to the next outer finally block
   as needed (see (5) below).

   CompletionType::Continue
   ========================
   Since continues and breaks work the same way (i.e. with a jump), we handle this
   exactly the same way as CompletionType::Break, except that we use the
   continueTarget instead of the breakTarget.

   CompletionType::Return
   ======================
   When we emit bytecode for the ReturnNode, we check if we have any FinallyContexts
   at all on the m_controlFlowScopeStack.  If we don't, then emit op_ret as usual.
   Otherwise:

   a. emit bytecode to set the completionTypeRegister to CompletionType::Return.
   b. emit bytecode to move the return value into the completionValueRegister.
   c. emit bytecode to jump to the normal entry of the innermost finally block.

   Each finally block will take care of cascading to the next outer finally block
   as needed (see (5) below).

   CompletionType::Throw
   ======================
   At the catch entry a finally block, we:
   1. emit an op_catch that stores the caught Exception object in the
      completionValueRegister.
   2. emit bytecode to set the completionTypeRegister to CompletionType::Throw.
   3. Fall through or jump to the finally block's normal entry.

5. What happens in each finally block?
   ==================================
   For details on the finally block's catch entry, see "CompletionType::Throw" in
   (4) above.

   The finally block's normal entry will:
   1. restore the scope of the finally block.
   2. save the completionTypeRegister in a savedCompletionTypeRegister.
   3. proceed to execute the body of the finally block.

   At the end of the finally block, we will emit bytecode check the
   savedCompletionTypeRegister for each completion type see emitFinallyCompletion())
   in the following order:

   a. Check for CompletionType::Normal
      ================================
      If savedCompletionTypeRegister is CompletionType::Normal, jump to the
      designated normalCompletion label.  We only need this check this finally
      block also needs to check for Break, Continue, or Return.  If not, the
      completion type check for CompletionType::Throw below will make this check
      redundant.

   b. Check for CompletionType::Break and Continue
      ============================================
      If the FinallyContext for this block has registered FinallyJumps, we'll
      check the jumpIDs against the savedCompletionTypeRegister.  If the jumpID
      matches, jump to the corresponding jumpTarget.

      If no jumpIDs match but the FinallyContext's numberOfBreaksOrContinues is
      greater than the number of registered FinallyJumps, then this means that
      we have a Break or Continue that needs to be handled by an outer finally
      block.  In that case, jump to the next outer finally block's normal entry.

   c. Check for CompletionType::Return
      ================================
      If this finally block is not the outermost and the savedCompletionTypeRegister
      is set to CompletionType::Return, then jump to the next outer finally
      block's normal entry.

      Otherwise, if this finally block is the outermost and the savedCompletionTypeRegister
      is set to CompletionType::Return, then execute op_ret and return the value
      in the completionValueRegister.

   d. CompletionType::Throw
      =====================
      If savedCompletionTypeRegister is CompletionType::Throw, then just re-throw the
      Exception object in the completionValueRegister.

   Detail 1: that we check the savedCompletionTypeRegister (and not the
   completionTypeRegister).  This is because the finally block may itself contain
   a try-finally, and this inner try-finally may have trashed the completionTypeRegister.
   Here's an example:

       try {
           return "r1"; // Sets completionTypeRegister to CompletionType::Return;
       } finally {
           // completionTypeRegister is CompletionType::Return here.

           try {
               ... // do stuff.
           } finally {
               ... // do more stuff.
           }

           // completionTypeRegister may be anything here depending on what
           // was executed in the inner try-finally block above.

           // Hence, finally completion here must be based on a saved copy of the
           // completionTypeRegister when we entered this finally block.
       }

   Detail 2: the finally completion for CompletionType::Throw must always explicitly
   check if the savedCompletionTypeRegister is CompletionType::Throw before throwing.
   We cannot imply that it is so from the Throw case being last.  Here's why:

       // completionTypeRegister is CompletionType::Normal here.
       try {
           return "r1"; // Sets completionTypeRegister to CompletionType::Return;
       } finally {
           // completionTypeRegister is CompletionType::Return here.

           try {
               ... // do stuff.  No abrupt completions.
           } finally {
               // completionTypeRegister is CompletionType::Return here (from the outer try-finally).
               // savedCompletionTypeRegister is set to completionTypeRegister (i.e. CompletionType::Return) here.

               ... // do more stuff.  No abrupt completions.

               // Unless there's an abrupt completion since entering the outer
               // finally block, the savedCompletionTypeRegister will remain set
               // to CompletionType::Return.  If we don't explicitly check if the
               // savedCompletionTypeRegister is CompletionType::Throw before
               // throwing here, we'll end up erroneously throwing "r1".
           }

           ...
       }

6. restoreScopeRegister()

   Since the needed scope objects are always stored in a local, we can restore
   the scope register by simply moving from that local instead of going through
   op_get_parent_scope.

7. m_controlFlowScopeStack needs to be a SegmentedVector instead of a Vector.
   This makes it easier to keep a pointer to the FinallyContext on that stack,
   and not have to worry about the vector being realloc'ed due to resizing.

Performance appears to be neutral both on ES6SampleBench (run via cli) and the
JSC benchmarks.

Relevant spec references:
https://tc39.github.io/ecma262/#sec-completion-record-specification-type
https://tc39.github.io/ecma262/#sec-try-statement-runtime-semantics-evaluation

* bytecode/HandlerInfo.h:
(JSC::HandlerInfoBase::typeName):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::generate):
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::emitReturn):
(JSC::BytecodeGenerator::pushFinallyControlFlowScope):
(JSC::BytecodeGenerator::popFinallyControlFlowScope):
(JSC::BytecodeGenerator::allocateAndEmitScope):
(JSC::BytecodeGenerator::pushTry):
(JSC::BytecodeGenerator::popTry):
(JSC::BytecodeGenerator::emitCatch):
(JSC::BytecodeGenerator::restoreScopeRegister):
(JSC::BytecodeGenerator::labelScopeDepthToLexicalScopeIndex):
(JSC::BytecodeGenerator::labelScopeDepth):
(JSC::BytecodeGenerator::pushLocalControlFlowScope):
(JSC::BytecodeGenerator::popLocalControlFlowScope):
(JSC::BytecodeGenerator::emitEnumeration):
(JSC::BytecodeGenerator::emitIsNumber):
(JSC::BytecodeGenerator::emitYield):
(JSC::BytecodeGenerator::emitDelegateYield):
(JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
(JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
(JSC::BytecodeGenerator::emitFinallyCompletion):
(JSC::BytecodeGenerator::allocateCompletionRecordRegisters):
(JSC::BytecodeGenerator::releaseCompletionRecordRegisters):
(JSC::BytecodeGenerator::emitJumpIf):
(JSC::BytecodeGenerator::pushIteratorCloseControlFlowScope): Deleted.
(JSC::BytecodeGenerator::popIteratorCloseControlFlowScope): Deleted.
(JSC::BytecodeGenerator::emitComplexPopScopes): Deleted.
(JSC::BytecodeGenerator::emitPopScopes): Deleted.
(JSC::BytecodeGenerator::popTryAndEmitCatch): Deleted.
* bytecompiler/BytecodeGenerator.h:
(JSC::bytecodeOffsetToJumpID):
(JSC::FinallyJump::FinallyJump):
(JSC::FinallyContext::FinallyContext):
(JSC::FinallyContext::outerContext):
(JSC::FinallyContext::finallyLabel):
(JSC::FinallyContext::depth):
(JSC::FinallyContext::numberOfBreaksOrContinues):
(JSC::FinallyContext::incNumberOfBreaksOrContinues):
(JSC::FinallyContext::handlesReturns):
(JSC::FinallyContext::setHandlesReturns):
(JSC::FinallyContext::registerJump):
(JSC::FinallyContext::numberOfJumps):
(JSC::FinallyContext::jumps):
(JSC::ControlFlowScope::ControlFlowScope):
(JSC::ControlFlowScope::isLabelScope):
(JSC::ControlFlowScope::isFinallyScope):
(JSC::BytecodeGenerator::currentLexicalScopeIndex):
(JSC::BytecodeGenerator::CompletionRecordScope::CompletionRecordScope):
(JSC::BytecodeGenerator::CompletionRecordScope::~CompletionRecordScope):
(JSC::BytecodeGenerator::completionTypeRegister):
(JSC::BytecodeGenerator::completionValueRegister):
(JSC::BytecodeGenerator::emitSetCompletionType):
(JSC::BytecodeGenerator::emitSetCompletionValue):
(JSC::BytecodeGenerator::isInFinallyBlock): Deleted.
* bytecompiler/NodesCodegen.cpp:
(JSC::ContinueNode::emitBytecode):
(JSC::BreakNode::emitBytecode):
(JSC::ReturnNode::emitBytecode):
(JSC::TryNode::emitBytecode):

Source/WTF:

Added some methods to bring SegmentedVector closer to parity with Vector.

* wtf/SegmentedVector.h:
(WTF::SegmentedVector::first):
(WTF::SegmentedVector::last):
(WTF::SegmentedVector::takeLast):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@210116 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/stress/deeply-nested-finallys.js [new file with mode: 0644]
JSTests/stress/test-finally.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/HandlerInfo.h
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
Source/WTF/ChangeLog
Source/WTF/wtf/SegmentedVector.h

index 93cf99e..038f57f 100644 (file)
@@ -1,3 +1,20 @@
+2016-12-22  Mark Lam  <mark.lam@apple.com>
+
+        De-duplicate finally blocks.
+        https://bugs.webkit.org/show_bug.cgi?id=160168
+
+        Reviewed by Saam Barati.
+
+        Re-landing r209952 with a few new tests added in test-finally.js.
+
+        * stress/deeply-nested-finallys.js: Copied from JSTests/stress/deeply-nested-finallys.js.
+        - Tests many levels of finally nesting.  This causes the old code to hang (and
+          crashes eventually) while trying to generate bytecode for the exponentially
+          duplicated finally blocks.  The new code completes this test almost instantly.
+
+        * stress/test-finally.js: Copied from JSTests/stress/test-finally.js.
+        - Tests control flow through various permutations of finally blocks.
+
 2016-12-22  Saam Barati  <sbarati@apple.com>
 
         WebAssembly: Make the spec-tests/address.wast.js test pass
diff --git a/JSTests/stress/deeply-nested-finallys.js b/JSTests/stress/deeply-nested-finallys.js
new file mode 100644 (file)
index 0000000..f7ff5fe
--- /dev/null
@@ -0,0 +1,78 @@
+// This test should finish almost instantly.
+
+function exp() {
+    try {
+        try {
+            try {
+                try { 
+                    try {
+                        try {
+                            try {
+                                try {
+                                    try {
+                                        try {
+                                            try {
+                                                try {
+                                                    try { 
+                                                        try {
+                                                            try {
+                                                                try {
+                                                                    try {
+                                                                        try {
+                                                                            try {
+                                                                                try {
+                                                                                    try {
+                                                                                        try { 
+                                                                                            try {
+                                                                                                try {
+                                                                                                    try {
+                                                                                                        try {
+                                                                                                            try {
+                                                                                                                try {
+                                                                                                                    try {
+                                                                                                                        try {
+                                                                                                                            try { 
+                                                                                                                                try {
+                                                                                                                                    try {
+                                                                                                                                        try {
+                                                                                                                                            try {
+                                                                                                                                                try {
+                                                                                                                                                } finally {return 1;}
+                                                                                                                                            } finally { return 1; }
+                                                                                                                                        } finally {return 1;}
+                                                                                                                                    } finally { return 1; }
+                                                                                                                                } finally {return 1;}
+                                                                                                                            } finally {return 1;}
+                                                                                                                        } finally {return 1;}
+                                                                                                                    } finally {return 1;}    
+                                                                                                                } finally { return 1; }
+                                                                                                            } finally {return 1;}
+                                                                                                        } finally { return 1; }
+                                                                                                    } finally {return 1;}
+                                                                                                } finally { return 1; }
+                                                                                            } finally {return 1;}
+                                                                                        } finally {return 1;}
+                                                                                    } finally {return 1;}
+                                                                                } finally {return 1;}    
+                                                                            } finally { return 1; }
+                                                                        } finally {return 1;}
+                                                                    } finally { return 1; }
+                                                                } finally {return 1;}
+                                                            } finally { return 1; }
+                                                        } finally {return 1;}
+                                                    } finally {return 1;}
+                                                } finally {return 1;}
+                                            } finally {return 1;}    
+                                        } finally { return 1; }
+                                    } finally {return 1;}
+                                } finally { return 1; }
+                            } finally {return 1;}
+                        } finally { return 1; }
+                    } finally {return 1;}
+                } finally {return 1;}
+            } finally {return 1;}
+        } finally {return 1;}    
+    } finally { return 1; }
+}
+
+exp();
diff --git a/JSTests/stress/test-finally.js b/JSTests/stress/test-finally.js
new file mode 100644 (file)
index 0000000..b8dcbf5
--- /dev/null
@@ -0,0 +1,1445 @@
+// This test just creates functions which exercise various permutations of control flow
+// thru finally blocks. The test passes if it does not throw any exceptions or crash on
+// bytecode validation.
+
+if (this.window)
+    print = console.log;
+
+var steps;
+var returned;
+var thrown;
+var testLineNumber;
+
+let NothingReturned = "NothingReturned";
+let NothingThrown = "NothingThrown";
+
+function assertResults(expectedSteps, expectedReturned, expectedThrown) {
+    let stepsMismatch = (steps != expectedSteps);
+    let returnedMismatch = (returned != expectedReturned);
+    let thrownMismatch = (thrown != expectedThrown && !("" + thrown).startsWith("" + expectedThrown));
+
+    if (stepsMismatch || returnedMismatch || thrownMismatch) {
+        print("FAILED Test @ line " + testLineNumber + ":");
+        print("   Actual:   steps: " + steps + ", returned: " + returned + ", thrown: '" + thrown + "'");
+        print("   Expected: steps: " + expectedSteps + ", returned: " + expectedReturned + ", thrown: '" + expectedThrown + "'");
+    }
+    if (stepsMismatch)
+        throw "FAILED Test @ line " + testLineNumber + ": steps do not match: expected ='" + expectedSteps + "' actual='" + steps + "'";
+    if (returnedMismatch)
+        throw "FAILED Test @ line " + testLineNumber + ": returned value does not match: expected ='" + expectedReturned + "' actual='" + returned + "'";
+    if (thrownMismatch)
+        throw "FAILED Test @ line " + testLineNumber + ": thrown value does does not match: expected ='" + expectedThrown + "' actual='" + thrown + "'";
+}
+
+function resetResults() {
+    steps = [];
+    returned = NothingReturned;
+    thrown = NothingThrown;
+}
+
+function append(step) {
+    let next = steps.length;
+    steps[next] = step;
+}
+
+function test(func, expectedSteps, expectedReturned, expectedThrown) {
+    testLineNumber = new Error().stack.match(/global code@.+\.js:([0-9]+)/)[1];
+    resetResults();
+
+    try {
+        returned = func();
+    } catch (e) {
+        thrown = e;
+    }
+
+    assertResults(expectedSteps, expectedReturned, expectedThrown);
+}
+
+// Test CompletionType::Normal on an empty try blocks.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,f1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Normal.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+                append("t1");
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Break.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+                append("t1");
+                break;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Continue.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+                append("t1");
+                continue;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Return.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+                append("t1");
+                return;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Throw.
+test(() =>  {
+    try {
+        append("t2");
+        for (var i = 0; i < 2; i++) {
+            try {
+                append("t1");
+                throw { };
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,c1,f1,t1,c1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Normal in a for-of loop.
+test(() =>  {
+    let arr = [1, 2];
+    try {
+        append("t2");
+        for (let x of arr) {
+            try {
+                append("t1");
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Break in a for-of loop.
+test(() =>  {
+    let arr = [1, 2];
+    try {
+        append("t2");
+        for (let x of arr) {
+            try {
+                append("t1");
+                break;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Continue in a for-of loop.
+test(() =>  {
+    let arr = [1, 2];
+    try {
+        append("t2");
+        for (let x of arr) {
+            try {
+                append("t1");
+                continue;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Return in a for-of loop.
+test(() =>  {
+    let arr = [1, 2];
+    try {
+        append("t2");
+        for (let x of arr) {
+            try {
+                append("t1");
+                return;
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Throw in a for-of loop.
+test(() =>  {
+    let arr = [1, 2];
+    try {
+        append("t2");
+        for (let x of arr) {
+            try {
+                append("t1");
+                throw { };
+            } catch(a) {
+                append("c1");
+            } finally {
+                append("f1");
+            }
+        }
+    } catch(b) {
+        append("c2");
+    } finally {
+        append("f2");
+    }
+
+}, "t2,t1,c1,f1,t1,c1,f1,f2", undefined, NothingThrown);
+
+
+// No abrupt completions.
+test(() => {
+    append("a");
+    try {
+        append("b");
+    } finally {
+        append("c");
+    }   
+    append("d");
+    return 400;
+
+}, "a,b,c,d", 400, NothingThrown);
+
+
+// Return after a. Should not execute any finally blocks, and return a's result.
+test(() => {
+    append("a");
+    return 100;
+    try {
+        append("b");
+        return 200;
+        try {
+            append("c");
+            return 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        return 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a", 100, NothingThrown);
+
+// Return after b. Should execute finally block f only, and return b's result.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 200;
+        try {
+            append("c");
+            return 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        return 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,f", 200, NothingThrown);
+
+// Return after c. Should execute finally blocks d and f, and return c's result.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            return 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        return 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,d,f", 300, NothingThrown);
+
+// Return after c and d. Should execute finally blocks d and f, and return last return value from d.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            return 300;
+        } finally {
+            append("d");
+            return 400;
+        }
+        append("e");
+        return 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,d,f", 400, NothingThrown);
+
+// Return after c, d, and f. Should execute finally blocks d and f, and return last return value from f.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            return 300;
+        } finally {
+            append("d");
+            return 400;
+        }
+        append("e");
+        return 500;
+    } finally {
+        append("f");
+        return 600;
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,d,f", 600, NothingThrown);
+
+// Return after g.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+        } finally {
+            append("d");
+        }
+        append("e");
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,d,e,f,g", 700, NothingThrown);
+
+// Throw after a.
+test(() => {
+    append("a");
+    throw 100;
+    try {
+        append("b");
+        throw 200;
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        throw 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    throw 700;
+
+}, "a", NothingReturned, 100);
+
+// Throw after b.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 200;
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        throw 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    throw 700;
+
+}, "a,b,f", NothingReturned, 200);
+
+// Throw after c.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        throw 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    throw 700;
+
+}, "a,b,c,d,f", NothingReturned, 300);
+
+// Throw after c and d.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+            throw 400;
+        }
+        append("e");
+        throw 500;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    throw 700;
+
+}, "a,b,c,d,f", NothingReturned, 400);
+
+// Throw after c, d, and f.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+            throw 400;
+        }
+        append("e");
+        throw 500;
+    } finally {
+        append("f");
+        throw 600;
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,d,f", NothingReturned, 600);
+
+// Throw after g.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+        } finally {
+            append("d");
+        }
+        append("e");
+    } finally {
+        append("f");
+    }   
+    append("g");
+    throw 700;
+
+}, "a,b,c,d,e,f,g", NothingReturned, 700);
+
+// Throw after b with catch at z.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 200;
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        throw 500;
+    } catch (e) {
+        append("z");
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,z,f,g", 700, NothingThrown);
+
+// Throw after b with catch at z and rethrow at z.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 200;
+        try {
+            append("c");
+            throw 300;
+        } finally {
+            append("d");
+        }
+        append("e");
+        throw 500;
+    } catch (e) {
+        append("z");
+        throw 5000;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,z,f", NothingReturned, 5000);
+
+// Throw after c with catch at y and z.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } catch (e) {
+            append("y");
+        } finally {
+            append("d");
+        }
+        append("e");
+    } catch (e) {
+        append("z");
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,f,g", 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } catch (e) {
+            append("y");
+            throw 3000;
+        } finally {
+            append("d");
+        }
+        append("e");
+    } catch (e) {
+        append("z");
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,z,f,g", 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y and z.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } catch (e) {
+            append("y");
+            throw 3000;
+        } finally {
+            append("d");
+        }
+        append("e");
+    } catch (e) {
+        append("z");
+        throw 5000;
+    } finally {
+        append("f");
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,z,f", NothingReturned, 5000);
+
+// Throw after c with catch at y and z, and rethrow at y, z, and f.
+test(() => {
+    append("a");
+    try {
+        append("b");
+        try {
+            append("c");
+            throw 300;
+        } catch (e) {
+            append("y");
+            throw 3000;
+        } finally {
+            append("d");
+        }
+        append("e");
+    } catch (e) {
+        append("z");
+        throw 5000;
+    } finally {
+        append("f");
+        throw 600;
+    }   
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,z,f", NothingReturned, 600);
+
+// No throw or return in for-of loop.
+test(() => {
+    class TestIterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,n,2,n,x", 200, NothingThrown);
+
+// Break in for-of loop.
+test(() => {
+    class TestIterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r,x", 200, NothingThrown);
+
+// Break in for-of loop with throw in Iterator.return().
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 300);
+
+// Break in for-of loop with Iterator.return() returning a non object.
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 'TypeError');
+
+// Return in for-of loop.
+test(() => {
+    class TestIterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", 100, NothingThrown);
+
+// Return in for-of loop with throw in Iterator.return().
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 300);
+
+// Return in for-of loop with Iterator.return() returning a non object.
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 'TypeError');
+
+
+// Throw in for-of loop.
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            throw 100;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 100);
+
+// Throw in for-of loop with throw in Iterator.return().
+test(() => {
+    class Iterator {
+        constructor() {
+            append("c");
+            this.i = 0;
+        }
+        next() {
+            append("n");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("r");
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            throw 100;
+    }
+    append("x");
+    return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 100);
+
+// Handling return in finally block F1 with try-finally in F1's body.
+test(() =>  {
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        try {
+            append("t2");
+        } catch (e) {
+            append("c2");
+        } finally {
+            append("f2");
+        }
+        append("f1b");
+    }
+
+}, "t1a,f1a,t2,f2,f1b", "r1", NothingThrown);
+
+// Handling return in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        try {
+            append("t1a");
+            return "r1";
+            append("t1b");
+        } catch(e) {
+            append("c1");
+        } finally {
+            append("f1a");
+
+            try {
+                append("t2");
+            } catch (e) {
+                append("c2");
+            } finally {
+                append("f2");
+            }
+            append("f1b");
+        }
+    }
+    append("x");
+
+}, "ci,ni,0,t1a,f1a,t2,f2,f1b,ri", "r1", NothingThrown);
+
+// Handling break in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        try {
+            append("t1a");
+            break;
+            append("t1b");
+        } catch(e) {
+            append("c1");
+        } finally {
+            append("f1a");
+
+            try {
+                append("t2");
+            } catch (e) {
+                append("c2");
+            } finally {
+                append("f2");
+            }
+            append("f1b");
+        }
+    }
+    append("x");
+
+}, "ci,ni,0,t1a,f1a,t2,f2,f1b,ri,x", undefined, NothingThrown);
+
+// Handling return in a for-of loop in finally block F1 with try-finally in F1's body.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        for (var element of arr) {
+            append(element);
+            return "r1";
+        }
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        try {
+            append("t2");
+        } catch (e) {
+            append("c2");
+        } finally {
+            append("f2");
+        }
+        append("f1b");
+    }
+    append("x");
+
+}, "t1a,ci,ni,0,ri,f1a,t2,f2,f1b", "r1", NothingThrown);
+
+// Handling break in a for-of loop in finally block F1 with try-finally in F1's body.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        for (var element of arr) {
+            append(element);
+            break;
+        }
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        try {
+            append("t2");
+        } catch (e) {
+            append("c2");
+        } finally {
+            append("f2");
+        }
+        append("f1b");
+    }
+    append("x");
+
+}, "t1a,ci,ni,0,ri,t1b,f1a,t2,f2,f1b,x", undefined, NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop in F1's body.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        for (var element of arr) {
+            append(element);
+        }
+        append("f1b");
+    }
+
+    append("x");
+
+}, "t1a,f1a,ci,ni,0,ni,1,ni,f1b", "r1", NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop nesting a try-finally in F1's body.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        for (var element of arr) {
+            append(element);
+            try {
+                append("t2");
+            } catch (e) {
+                append("c2");
+            } finally {
+                append("f2");
+            }
+        }
+        append("f1b");
+    }
+
+    append("x");
+
+}, "t1a,f1a,ci,ni,0,t2,f2,ni,1,t2,f2,ni,f1b", "r1", NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop in F1's body + break in for-of loop.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        for (var element of arr) {
+            append(element);
+            break;
+        }
+        append("f1b");
+    }
+
+    append("x");
+
+}, "t1a,f1a,ci,ni,0,ri,f1b", "r1", NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop nesting a try-finally in F1's body + break in for-of loop.
+test(() =>  {
+    class TestIterator {
+        constructor() {
+            append("ci");
+            this.i = 0;
+        }
+        next() {
+            append("ni");
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append("ri");
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        for (var element of arr) {
+            append(element);
+            try {
+                append("t2");
+            } catch (e) {
+                append("c2");
+            } finally {
+                append("f2");
+            }
+            break;
+        }
+        append("f1b");
+    }
+
+    append("x");
+
+}, "t1a,f1a,ci,ni,0,t2,f2,ri,f1b", "r1", NothingThrown);
+
+// Handling return in finally block F1 with try-finally in F1's body.
+test(() =>  {
+    try {
+        append("t1a");
+        return "r1";
+        append("t1b");
+    } catch(e) {
+        append("c1");
+    } finally {
+        append("f1a");
+
+        try {
+            append("t2");
+            throw "t2";
+        } catch (e) {
+            append("c2");
+            // t2 caught here, and completion type set back to normal.
+        } finally {
+            append("f2");
+        }
+        append("f1b");
+    }
+
+}, "t1a,f1a,t2,c2,f2,f1b", "r1", NothingThrown);
+
+if (this.window)
+    print("PASSED");
index 0bd0c94..97c0903 100644 (file)
@@ -1,3 +1,295 @@
+2016-12-22  Mark Lam  <mark.lam@apple.com>
+
+        De-duplicate finally blocks.
+        https://bugs.webkit.org/show_bug.cgi?id=160168
+
+        Reviewed by Saam Barati.
+
+        JS execution can arrive at a finally block when there are abrupt completions from
+        its try or catch block.  The abrupt completion types include Break,
+        Continue, Return, and Throw.  The non-abrupt completion type is called Normal
+        (i.e. the case of a try block falling through to the finally block).
+
+        Previously, we enable each of these paths for abrupt completion (except for Throw)
+        to run the finally block code by duplicating the finally block code at each of
+        the sites that trigger those completions.  This patch fixes the implementation so
+        that each of these abrupt completions will set a completionTypeRegister (plus a
+        completionValueRegister for CompletionType::Return) and then jump to the
+        relevant finally blocks, and continue to thread through subsequent outer finally
+        blocks until execution reaches the outermost finally block that the completion
+        type dictates.  We no longer duplicate the finally block code.
+
+        The implementation details:
+        1. We allocate a pair of registers (completionTypeRegister and completionValueRegister)
+           just before entering the outermost try-catch-finally scope.
+
+           On allocating the registers, we initialize the completionTypeRegister to
+           CompletionType::Normal, and set the completionValueRegister to the empty
+           JSValue.
+
+        2. The completionTypeRegister will hold a CompletionType value.  This is how we
+           encode the CompletionType value to be set:
+
+           a. For Normal, Return, and Throw completion types: 
+              - The completionTypeRegister is set to CompletionType::Normal,
+                CompletionType::Return, and CompletionType::Throw respectively.
+
+           b. For Break and Continue completion types:
+              - The completionTypeRegister is set to a unique jumpID where the jumpID is
+                computed as:
+
+                jumpID = CompletionType::NumberOfTypes + bytecodeOffset
+
+                The bytecodeOffset used here is the bytecodeOffset of the break or continue
+                statement that triggered this completion.
+
+        3. Each finally block will have 2 entries:
+           a. the catch entry.
+           b. the normal entry.
+
+           The catch entry is recorded in the codeBlock's exception handler table,
+           and can only be jumped to by the VM's exception handling mechanism.
+
+           The normal entry is recorded in a FinallyContext (at bytecode generation time
+           only) and is jumped to when we want enter the finally block due any of the
+           other CompletionTypes.
+
+        4. How each completion type works?
+
+           CompletionType::Normal
+           ======================
+           We normally encounter this when falling through from a try or catch block to
+           the finally block.  
+          
+           For the try block case, since completionTypeRegister is set to Normal by default,
+           there's nothing more that needs to be done.
+
+           For the catch block case, since we entered the catch block with an exception,
+           completionTypeRegister may be set to Throw.  We'll need to set it to Normal
+           before jumping to the finally block's normal entry.
+
+           CompletionType::Break
+           =====================
+           When we emit bytecode for the BreakNode, we check if we have any FinallyContexts
+           that we need to service before jumping to the breakTarget.  If we don't, then
+           emit op_jump to the breakTarget as usual.  Otherwise:
+
+           a. we'll register a jumpID and the breakTarget with the FinallyContext for the
+              outermost finally block that we're supposed to run through.
+           b. we'll also increment the numberOfBreaksOrContinues count in each FinallyContext
+              from the innermost to the one for that outermost finally block.
+           c. emit bytecode to set the completionTypeRegister to the jumpID.
+           d. emit bytecode to jump to the normal entry of the innermost finally block.
+
+           Each finally block will take care of cascading to the next outer finally block
+           as needed (see (5) below).
+
+           CompletionType::Continue
+           ========================
+           Since continues and breaks work the same way (i.e. with a jump), we handle this
+           exactly the same way as CompletionType::Break, except that we use the
+           continueTarget instead of the breakTarget.
+
+           CompletionType::Return
+           ======================
+           When we emit bytecode for the ReturnNode, we check if we have any FinallyContexts
+           at all on the m_controlFlowScopeStack.  If we don't, then emit op_ret as usual.
+           Otherwise:
+
+           a. emit bytecode to set the completionTypeRegister to CompletionType::Return.
+           b. emit bytecode to move the return value into the completionValueRegister.
+           c. emit bytecode to jump to the normal entry of the innermost finally block.
+
+           Each finally block will take care of cascading to the next outer finally block
+           as needed (see (5) below).
+
+           CompletionType::Throw
+           ======================
+           At the catch entry a finally block, we:
+           1. emit an op_catch that stores the caught Exception object in the
+              completionValueRegister.
+           2. emit bytecode to set the completionTypeRegister to CompletionType::Throw.
+           3. Fall through or jump to the finally block's normal entry.
+
+        5. What happens in each finally block?
+           ==================================
+           For details on the finally block's catch entry, see "CompletionType::Throw" in
+           (4) above.
+
+           The finally block's normal entry will:
+           1. restore the scope of the finally block.
+           2. save the completionTypeRegister in a savedCompletionTypeRegister.
+           3. proceed to execute the body of the finally block.
+
+           At the end of the finally block, we will emit bytecode check the
+           savedCompletionTypeRegister for each completion type see emitFinallyCompletion())
+           in the following order:
+          
+           a. Check for CompletionType::Normal
+              ================================
+              If savedCompletionTypeRegister is CompletionType::Normal, jump to the
+              designated normalCompletion label.  We only need this check this finally
+              block also needs to check for Break, Continue, or Return.  If not, the
+              completion type check for CompletionType::Throw below will make this check
+              redundant.
+
+           b. Check for CompletionType::Break and Continue
+              ============================================
+              If the FinallyContext for this block has registered FinallyJumps, we'll
+              check the jumpIDs against the savedCompletionTypeRegister.  If the jumpID
+              matches, jump to the corresponding jumpTarget.
+
+              If no jumpIDs match but the FinallyContext's numberOfBreaksOrContinues is
+              greater than the number of registered FinallyJumps, then this means that
+              we have a Break or Continue that needs to be handled by an outer finally
+              block.  In that case, jump to the next outer finally block's normal entry.
+             
+           c. Check for CompletionType::Return
+              ================================
+              If this finally block is not the outermost and the savedCompletionTypeRegister
+              is set to CompletionType::Return, then jump to the next outer finally
+              block's normal entry.
+
+              Otherwise, if this finally block is the outermost and the savedCompletionTypeRegister
+              is set to CompletionType::Return, then execute op_ret and return the value
+              in the completionValueRegister.
+
+           d. CompletionType::Throw
+              =====================
+              If savedCompletionTypeRegister is CompletionType::Throw, then just re-throw the
+              Exception object in the completionValueRegister.
+
+           Detail 1: that we check the savedCompletionTypeRegister (and not the
+           completionTypeRegister).  This is because the finally block may itself contain
+           a try-finally, and this inner try-finally may have trashed the completionTypeRegister.
+           Here's an example:
+
+               try {
+                   return "r1"; // Sets completionTypeRegister to CompletionType::Return;
+               } finally {
+                   // completionTypeRegister is CompletionType::Return here.
+
+                   try {
+                       ... // do stuff.
+                   } finally {
+                       ... // do more stuff.
+                   }
+
+                   // completionTypeRegister may be anything here depending on what
+                   // was executed in the inner try-finally block above.
+
+                   // Hence, finally completion here must be based on a saved copy of the
+                   // completionTypeRegister when we entered this finally block.
+               }
+
+           Detail 2: the finally completion for CompletionType::Throw must always explicitly
+           check if the savedCompletionTypeRegister is CompletionType::Throw before throwing.
+           We cannot imply that it is so from the Throw case being last.  Here's why:
+
+               // completionTypeRegister is CompletionType::Normal here.
+               try {
+                   return "r1"; // Sets completionTypeRegister to CompletionType::Return;
+               } finally {
+                   // completionTypeRegister is CompletionType::Return here.
+
+                   try {
+                       ... // do stuff.  No abrupt completions.
+                   } finally {
+                       // completionTypeRegister is CompletionType::Return here (from the outer try-finally).
+                       // savedCompletionTypeRegister is set to completionTypeRegister (i.e. CompletionType::Return) here.
+
+                       ... // do more stuff.  No abrupt completions.
+
+                       // Unless there's an abrupt completion since entering the outer
+                       // finally block, the savedCompletionTypeRegister will remain set
+                       // to CompletionType::Return.  If we don't explicitly check if the
+                       // savedCompletionTypeRegister is CompletionType::Throw before
+                       // throwing here, we'll end up erroneously throwing "r1".
+                   }
+
+                   ...
+               }
+
+        6. restoreScopeRegister()
+       
+           Since the needed scope objects are always stored in a local, we can restore
+           the scope register by simply moving from that local instead of going through
+           op_get_parent_scope.
+
+        7. m_controlFlowScopeStack needs to be a SegmentedVector instead of a Vector.
+           This makes it easier to keep a pointer to the FinallyContext on that stack,
+           and not have to worry about the vector being realloc'ed due to resizing. 
+
+        Performance appears to be neutral both on ES6SampleBench (run via cli) and the
+        JSC benchmarks.
+
+        Relevant spec references:
+        https://tc39.github.io/ecma262/#sec-completion-record-specification-type
+        https://tc39.github.io/ecma262/#sec-try-statement-runtime-semantics-evaluation
+
+        * bytecode/HandlerInfo.h:
+        (JSC::HandlerInfoBase::typeName):
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::generate):
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        (JSC::BytecodeGenerator::emitReturn):
+        (JSC::BytecodeGenerator::pushFinallyControlFlowScope):
+        (JSC::BytecodeGenerator::popFinallyControlFlowScope):
+        (JSC::BytecodeGenerator::allocateAndEmitScope):
+        (JSC::BytecodeGenerator::pushTry):
+        (JSC::BytecodeGenerator::popTry):
+        (JSC::BytecodeGenerator::emitCatch):
+        (JSC::BytecodeGenerator::restoreScopeRegister):
+        (JSC::BytecodeGenerator::labelScopeDepthToLexicalScopeIndex):
+        (JSC::BytecodeGenerator::labelScopeDepth):
+        (JSC::BytecodeGenerator::pushLocalControlFlowScope):
+        (JSC::BytecodeGenerator::popLocalControlFlowScope):
+        (JSC::BytecodeGenerator::emitEnumeration):
+        (JSC::BytecodeGenerator::emitIsNumber):
+        (JSC::BytecodeGenerator::emitYield):
+        (JSC::BytecodeGenerator::emitDelegateYield):
+        (JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
+        (JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
+        (JSC::BytecodeGenerator::emitFinallyCompletion):
+        (JSC::BytecodeGenerator::allocateCompletionRecordRegisters):
+        (JSC::BytecodeGenerator::releaseCompletionRecordRegisters):
+        (JSC::BytecodeGenerator::emitJumpIf):
+        (JSC::BytecodeGenerator::pushIteratorCloseControlFlowScope): Deleted.
+        (JSC::BytecodeGenerator::popIteratorCloseControlFlowScope): Deleted.
+        (JSC::BytecodeGenerator::emitComplexPopScopes): Deleted.
+        (JSC::BytecodeGenerator::emitPopScopes): Deleted.
+        (JSC::BytecodeGenerator::popTryAndEmitCatch): Deleted.
+        * bytecompiler/BytecodeGenerator.h:
+        (JSC::bytecodeOffsetToJumpID):
+        (JSC::FinallyJump::FinallyJump):
+        (JSC::FinallyContext::FinallyContext):
+        (JSC::FinallyContext::outerContext):
+        (JSC::FinallyContext::finallyLabel):
+        (JSC::FinallyContext::depth):
+        (JSC::FinallyContext::numberOfBreaksOrContinues):
+        (JSC::FinallyContext::incNumberOfBreaksOrContinues):
+        (JSC::FinallyContext::handlesReturns):
+        (JSC::FinallyContext::setHandlesReturns):
+        (JSC::FinallyContext::registerJump):
+        (JSC::FinallyContext::numberOfJumps):
+        (JSC::FinallyContext::jumps):
+        (JSC::ControlFlowScope::ControlFlowScope):
+        (JSC::ControlFlowScope::isLabelScope):
+        (JSC::ControlFlowScope::isFinallyScope):
+        (JSC::BytecodeGenerator::currentLexicalScopeIndex):
+        (JSC::BytecodeGenerator::CompletionRecordScope::CompletionRecordScope):
+        (JSC::BytecodeGenerator::CompletionRecordScope::~CompletionRecordScope):
+        (JSC::BytecodeGenerator::completionTypeRegister):
+        (JSC::BytecodeGenerator::completionValueRegister):
+        (JSC::BytecodeGenerator::emitSetCompletionType):
+        (JSC::BytecodeGenerator::emitSetCompletionValue):
+        (JSC::BytecodeGenerator::isInFinallyBlock): Deleted.
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::ContinueNode::emitBytecode):
+        (JSC::BreakNode::emitBytecode):
+        (JSC::ReturnNode::emitBytecode):
+        (JSC::TryNode::emitBytecode):
+
 2016-12-22  Saam Barati  <sbarati@apple.com>
 
         WebAssembly: Make the spec-tests/address.wast.js test pass
index 0b1bd3b..752defe 100644 (file)
@@ -31,9 +31,9 @@
 namespace JSC {
 
 enum class HandlerType {
-    Illegal = 0,
-    Catch = 1,
-    Finally = 2,
+    Catch = 0,
+    Finally = 1,
+    SynthesizedCatch = 2,
     SynthesizedFinally = 3
 };
 
@@ -53,6 +53,8 @@ struct HandlerInfoBase {
             return "catch";
         case HandlerType::Finally:
             return "finally";
+        case HandlerType::SynthesizedCatch:
+            return "synthesized catch";
         case HandlerType::SynthesizedFinally:
             return "synthesized finally";
         default:
index 6ee770b..df08d2c 100644 (file)
@@ -155,7 +155,6 @@ ParserError BytecodeGenerator::generate()
         if (end <= start)
             continue;
         
-        ASSERT(range.tryData->handlerType != HandlerType::Illegal);
         UnlinkedHandlerInfo info(static_cast<uint32_t>(start), static_cast<uint32_t>(end),
             static_cast<uint32_t>(range.tryData->target->bind()), range.tryData->handlerType);
         m_codeBlock->addExceptionHandler(info);
@@ -678,21 +677,25 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
     // because a function's default parameter ExpressionNodes will use temporary registers.
     pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize, TDZRequirement::UnderTDZ);
 
+    RefPtr<Label> catchLabel = newLabel();
     TryData* tryFormalParametersData = nullptr;
-    if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
+    bool needTryCatch = isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList;
+    if (needTryCatch) {
         RefPtr<Label> tryFormalParametersStart = emitLabel(newLabel().get());
-        tryFormalParametersData = pushTry(tryFormalParametersStart.get());
+        tryFormalParametersData = pushTry(tryFormalParametersStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
     }
 
     initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures, shouldCreateArgumentsVariableInParameterScope);
 
-    if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
+    if (needTryCatch) {
         RefPtr<Label> didNotThrow = newLabel();
         emitJump(didNotThrow.get());
-        RefPtr<RegisterID> exception = newTemporary();
+        emitLabel(catchLabel.get());
+        popTry(tryFormalParametersData, catchLabel.get());
+
         RefPtr<RegisterID> thrownValue = newTemporary();
-        RefPtr<Label> catchHere = emitLabel(newLabel().get());
-        popTryAndEmitCatch(tryFormalParametersData, exception.get(), thrownValue.get(), catchHere.get(), HandlerType::Catch);
+        RegisterID* unused = newTemporary();
+        emitCatch(unused, thrownValue.get());
 
         // return promiseCapability.@reject(thrownValue)
         RefPtr<RegisterID> reject = emitGetById(newTemporary(), promiseCapabilityRegister(), m_vm->propertyNames->builtinNames().rejectPrivateName());
@@ -3491,16 +3494,16 @@ void BytecodeGenerator::emitCallDefineProperty(RegisterID* newObj, RegisterID* p
     }
 }
 
-RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
+RegisterID* BytecodeGenerator::emitReturn(RegisterID* src, ReturnFrom from)
 {
     if (isConstructor()) {
         bool mightBeDerived = constructorKind() == ConstructorKind::Extends;
         bool srcIsThis = src->index() == m_thisRegister.index();
 
-        if (mightBeDerived && srcIsThis)
+        if (mightBeDerived && (srcIsThis || from == ReturnFrom::Finally))
             emitTDZCheck(src);
 
-        if (!srcIsThis) {
+        if (!srcIsThis || from == ReturnFrom::Finally) {
             RefPtr<Label> isObjectLabel = newLabel();
             emitJumpIfTrue(emitIsObject(newTemporary(), src), isObjectLabel.get());
 
@@ -3678,80 +3681,29 @@ void BytecodeGenerator::emitWillLeaveCallFrameDebugHook()
     emitDebugHook(WillLeaveCallFrame, m_scopeNode->lastLine(), m_scopeNode->startOffset(), m_scopeNode->lineStartOffset());
 }
 
-void BytecodeGenerator::pushFinallyControlFlowScope(StatementNode* finallyBlock)
+FinallyContext* BytecodeGenerator::pushFinallyControlFlowScope(Label* finallyLabel)
 {
     // Reclaim free label scopes.
     while (m_labelScopes.size() && !m_labelScopes.last().refCount())
         m_labelScopes.removeLast();
 
-    ControlFlowScope scope;
-    scope.isFinallyBlock = true;
-    FinallyContext context = {
-        finallyBlock,
-        nullptr,
-        nullptr,
-        static_cast<unsigned>(m_controlFlowScopeStack.size()),
-        static_cast<unsigned>(m_switchContextStack.size()),
-        static_cast<unsigned>(m_forInContextStack.size()),
-        static_cast<unsigned>(m_tryContextStack.size()),
-        static_cast<unsigned>(m_labelScopes.size()),
-        static_cast<unsigned>(m_lexicalScopeStack.size()),
-        m_finallyDepth,
-        m_localScopeDepth
-    };
-    scope.finallyContext = context;
-    m_controlFlowScopeStack.append(scope);
-    m_finallyDepth++;
-}
-
-void BytecodeGenerator::pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* node)
-{
-    // Reclaim free label scopes.
-    while (m_labelScopes.size() && !m_labelScopes.last().refCount())
-        m_labelScopes.removeLast();
+    ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), FinallyContext(m_currentFinallyContext, finallyLabel, m_finallyDepth));
+    m_controlFlowScopeStack.append(WTFMove(scope));
 
-    ControlFlowScope scope;
-    scope.isFinallyBlock = true;
-    FinallyContext context = {
-        nullptr,
-        iterator,
-        node,
-        static_cast<unsigned>(m_controlFlowScopeStack.size()),
-        static_cast<unsigned>(m_switchContextStack.size()),
-        static_cast<unsigned>(m_forInContextStack.size()),
-        static_cast<unsigned>(m_tryContextStack.size()),
-        static_cast<unsigned>(m_labelScopes.size()),
-        static_cast<unsigned>(m_lexicalScopeStack.size()),
-        m_finallyDepth,
-        m_localScopeDepth
-    };
-    scope.finallyContext = context;
-    m_controlFlowScopeStack.append(scope);
     m_finallyDepth++;
+    m_currentFinallyContext = &m_controlFlowScopeStack.last().finallyContext;
+    return m_currentFinallyContext;
 }
 
-void BytecodeGenerator::popFinallyControlFlowScope()
+FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
 {
     ASSERT(m_controlFlowScopeStack.size());
-    ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
-    ASSERT(m_controlFlowScopeStack.last().finallyContext.finallyBlock);
-    ASSERT(!m_controlFlowScopeStack.last().finallyContext.iterator);
-    ASSERT(!m_controlFlowScopeStack.last().finallyContext.enumerationNode);
+    ASSERT(m_controlFlowScopeStack.last().isFinallyScope());
     ASSERT(m_finallyDepth > 0);
-    m_controlFlowScopeStack.removeLast();
-    m_finallyDepth--;
-}
-
-void BytecodeGenerator::popIteratorCloseControlFlowScope()
-{
-    ASSERT(m_controlFlowScopeStack.size());
-    ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
-    ASSERT(!m_controlFlowScopeStack.last().finallyContext.finallyBlock);
-    ASSERT(m_controlFlowScopeStack.last().finallyContext.iterator);
-    ASSERT(m_controlFlowScopeStack.last().finallyContext.enumerationNode);
-    ASSERT(m_finallyDepth > 0);
-    m_controlFlowScopeStack.removeLast();
+    ASSERT(m_currentFinallyContext);
+    m_currentFinallyContext = m_currentFinallyContext->outerContext();
     m_finallyDepth--;
+    return m_controlFlowScopeStack.takeLast().finallyContext;
 }
 
 LabelScopePtr BytecodeGenerator::breakTarget(const Identifier& name)
@@ -3851,162 +3803,12 @@ void BytecodeGenerator::allocateAndEmitScope()
     m_topMostScope = addVar();
     emitMove(m_topMostScope, scopeRegister());
 }
-    
-void BytecodeGenerator::emitComplexPopScopes(RegisterID* scope, ControlFlowScope* topScope, ControlFlowScope* bottomScope)
-{
-    while (topScope > bottomScope) {
-        // First we count the number of dynamic scopes we need to remove to get
-        // to a finally block.
-        int numberOfNormalScopes = 0;
-        while (topScope > bottomScope) {
-            if (topScope->isFinallyBlock)
-                break;
-            ++numberOfNormalScopes;
-            --topScope;
-        }
-
-        if (numberOfNormalScopes) {
-            // We need to remove a number of dynamic scopes to get to the next
-            // finally block
-            RefPtr<RegisterID> parentScope = newTemporary();
-            while (numberOfNormalScopes--) {
-                parentScope = emitGetParentScope(parentScope.get(), scope);
-                emitMove(scope, parentScope.get());
-            }
-
-            // If topScope == bottomScope then there isn't a finally block left to emit.
-            if (topScope == bottomScope)
-                return;
-        }
-        
-        Vector<ControlFlowScope> savedControlFlowScopeStack;
-        Vector<SwitchInfo> savedSwitchContextStack;
-        Vector<RefPtr<ForInContext>> savedForInContextStack;
-        Vector<TryContext> poppedTryContexts;
-        Vector<LexicalScopeStackEntry> savedLexicalScopeStack;
-        LabelScopeStore savedLabelScopes;
-        while (topScope > bottomScope && topScope->isFinallyBlock) {
-            RefPtr<Label> beforeFinally = emitLabel(newLabel().get());
-            
-            // Save the current state of the world while instating the state of the world
-            // for the finally block.
-            FinallyContext finallyContext = topScope->finallyContext;
-            bool flipScopes = finallyContext.controlFlowScopeStackSize != m_controlFlowScopeStack.size();
-            bool flipSwitches = finallyContext.switchContextStackSize != m_switchContextStack.size();
-            bool flipForIns = finallyContext.forInContextStackSize != m_forInContextStack.size();
-            bool flipTries = finallyContext.tryContextStackSize != m_tryContextStack.size();
-            bool flipLabelScopes = finallyContext.labelScopesSize != m_labelScopes.size();
-            bool flipLexicalScopeStack = finallyContext.lexicalScopeStackSize != m_lexicalScopeStack.size();
-            int topScopeIndex = -1;
-            int bottomScopeIndex = -1;
-            if (flipScopes) {
-                topScopeIndex = topScope - m_controlFlowScopeStack.begin();
-                bottomScopeIndex = bottomScope - m_controlFlowScopeStack.begin();
-                savedControlFlowScopeStack = m_controlFlowScopeStack;
-                m_controlFlowScopeStack.shrink(finallyContext.controlFlowScopeStackSize);
-            }
-            if (flipSwitches) {
-                savedSwitchContextStack = m_switchContextStack;
-                m_switchContextStack.shrink(finallyContext.switchContextStackSize);
-            }
-            if (flipForIns) {
-                savedForInContextStack = m_forInContextStack;
-                m_forInContextStack.shrink(finallyContext.forInContextStackSize);
-            }
-            if (flipTries) {
-                while (m_tryContextStack.size() != finallyContext.tryContextStackSize) {
-                    ASSERT(m_tryContextStack.size() > finallyContext.tryContextStackSize);
-                    TryContext context = m_tryContextStack.takeLast();
-                    TryRange range;
-                    range.start = context.start;
-                    range.end = beforeFinally;
-                    range.tryData = context.tryData;
-                    m_tryRanges.append(range);
-                    poppedTryContexts.append(context);
-                }
-            }
-            if (flipLabelScopes) {
-                savedLabelScopes = m_labelScopes;
-                while (m_labelScopes.size() > finallyContext.labelScopesSize)
-                    m_labelScopes.removeLast();
-            }
-            if (flipLexicalScopeStack) {
-                savedLexicalScopeStack = m_lexicalScopeStack;
-                m_lexicalScopeStack.shrink(finallyContext.lexicalScopeStackSize);
-            }
-            int savedFinallyDepth = m_finallyDepth;
-            m_finallyDepth = finallyContext.finallyDepth;
-            int savedDynamicScopeDepth = m_localScopeDepth;
-            m_localScopeDepth = finallyContext.dynamicScopeDepth;
-            
-            if (finallyContext.finallyBlock) {
-                // Emit the finally block.
-                emitNode(finallyContext.finallyBlock);
-            } else {
-                // Emit the IteratorClose block.
-                ASSERT(finallyContext.iterator);
-                emitIteratorClose(finallyContext.iterator, finallyContext.enumerationNode);
-            }
-
-            RefPtr<Label> afterFinally = emitLabel(newLabel().get());
-            
-            // Restore the state of the world.
-            if (flipScopes) {
-                m_controlFlowScopeStack = savedControlFlowScopeStack;
-                topScope = &m_controlFlowScopeStack[topScopeIndex]; // assert it's within bounds
-                bottomScope = m_controlFlowScopeStack.begin() + bottomScopeIndex; // don't assert, since it the index might be -1.
-            }
-            if (flipSwitches)
-                m_switchContextStack = savedSwitchContextStack;
-            if (flipForIns)
-                m_forInContextStack = savedForInContextStack;
-            if (flipTries) {
-                ASSERT(m_tryContextStack.size() == finallyContext.tryContextStackSize);
-                for (unsigned i = poppedTryContexts.size(); i--;) {
-                    TryContext context = poppedTryContexts[i];
-                    context.start = afterFinally;
-                    m_tryContextStack.append(context);
-                }
-                poppedTryContexts.clear();
-            }
-            if (flipLabelScopes)
-                m_labelScopes = savedLabelScopes;
-            if (flipLexicalScopeStack)
-                m_lexicalScopeStack = savedLexicalScopeStack;
-            m_finallyDepth = savedFinallyDepth;
-            m_localScopeDepth = savedDynamicScopeDepth;
-            
-            --topScope;
-        }
-    }
-}
 
-void BytecodeGenerator::emitPopScopes(RegisterID* scope, int targetScopeDepth)
-{
-    ASSERT(labelScopeDepth() - targetScopeDepth >= 0);
-
-    size_t scopeDelta = labelScopeDepth() - targetScopeDepth;
-    ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
-    if (!scopeDelta)
-        return;
-
-    if (!m_finallyDepth) {
-        RefPtr<RegisterID> parentScope = newTemporary();
-        while (scopeDelta--) {
-            parentScope = emitGetParentScope(parentScope.get(), scope);
-            emitMove(scope, parentScope.get());
-        }
-        return;
-    }
-
-    emitComplexPopScopes(scope, &m_controlFlowScopeStack.last(), &m_controlFlowScopeStack.last() - scopeDelta);
-}
-
-TryData* BytecodeGenerator::pushTry(Label* start)
+TryData* BytecodeGenerator::pushTry(Label* start, Label* handlerLabel, HandlerType handlerType)
 {
     TryData tryData;
-    tryData.target = newLabel();
-    tryData.handlerType = HandlerType::Illegal;
+    tryData.target = handlerLabel;
+    tryData.handlerType = handlerType;
     m_tryData.append(tryData);
     TryData* result = &m_tryData.last();
     
@@ -4019,7 +3821,7 @@ TryData* BytecodeGenerator::pushTry(Label* start)
     return result;
 }
 
-void BytecodeGenerator::popTryAndEmitCatch(TryData* tryData, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType handlerType)
+void BytecodeGenerator::popTry(TryData* tryData, Label* end)
 {
     m_usesExceptions = true;
     
@@ -4031,26 +3833,50 @@ void BytecodeGenerator::popTryAndEmitCatch(TryData* tryData, RegisterID* excepti
     tryRange.tryData = m_tryContextStack.last().tryData;
     m_tryRanges.append(tryRange);
     m_tryContextStack.removeLast();
-    
-    emitLabel(tryRange.tryData->target.get());
-    tryRange.tryData->handlerType = handlerType;
+}
 
+void BytecodeGenerator::emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister)
+{
     emitOpcode(op_catch);
     instructions().append(exceptionRegister->index());
     instructions().append(thrownValueRegister->index());
+}
 
-    bool foundLocalScope = false;
-    for (unsigned i = m_lexicalScopeStack.size(); i--; ) {
-        // Note that if we don't find a local scope in the current function/program, 
-        // we must grab the outer-most scope of this bytecode generation.
-        if (m_lexicalScopeStack[i].m_scope) {
-            foundLocalScope = true;
-            emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
-            break;
+void BytecodeGenerator::restoreScopeRegister(int lexicalScopeIndex)
+{
+    if (lexicalScopeIndex == CurrentLexicalScopeIndex)
+        return; // No change needed.
+
+    if (lexicalScopeIndex != OutermostLexicalScopeIndex) {
+        ASSERT(lexicalScopeIndex < static_cast<int>(m_lexicalScopeStack.size()));
+        int endIndex = lexicalScopeIndex + 1;
+        for (size_t i = endIndex; i--; ) {
+            if (m_lexicalScopeStack[i].m_scope) {
+                emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
+                return;
+            }
         }
     }
-    if (!foundLocalScope)
-        emitMove(scopeRegister(), m_topMostScope);
+    // Note that if we don't find a local scope in the current function/program,
+    // we must grab the outer-most scope of this bytecode generation.
+    emitMove(scopeRegister(), m_topMostScope);
+}
+
+void BytecodeGenerator::restoreScopeRegister()
+{
+    restoreScopeRegister(currentLexicalScopeIndex());
+}
+
+int BytecodeGenerator::labelScopeDepthToLexicalScopeIndex(int targetLabelScopeDepth)
+{
+    ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
+    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+    ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
+    if (!scopeDelta)
+        return CurrentLexicalScopeIndex;
+
+    ControlFlowScope& targetScope = m_controlFlowScopeStack[targetLabelScopeDepth];
+    return targetScope.lexicalScopeIndex;
 }
 
 int BytecodeGenerator::localScopeDepth() const
@@ -4059,8 +3885,10 @@ int BytecodeGenerator::localScopeDepth() const
 }
 
 int BytecodeGenerator::labelScopeDepth() const
-{ 
-    return localScopeDepth() + m_finallyDepth;
+{
+    int depth = localScopeDepth() + m_finallyDepth;
+    ASSERT(depth == static_cast<int>(m_controlFlowScopeStack.size()));
+    return depth;
 }
 
 void BytecodeGenerator::emitThrowStaticError(ErrorType errorType, RegisterID* raw)
@@ -4130,16 +3958,15 @@ void BytecodeGenerator::emitPushFunctionNameScope(const Identifier& property, Re
 
 void BytecodeGenerator::pushLocalControlFlowScope()
 {
-    ControlFlowScope scope;
-    scope.isFinallyBlock = false;
-    m_controlFlowScopeStack.append(scope);
+    ControlFlowScope scope(ControlFlowScope::Label, currentLexicalScopeIndex());
+    m_controlFlowScopeStack.append(WTFMove(scope));
     m_localScopeDepth++;
 }
 
 void BytecodeGenerator::popLocalControlFlowScope()
 {
     ASSERT(m_controlFlowScopeStack.size());
-    ASSERT(!m_controlFlowScopeStack.last().isFinallyBlock);
+    ASSERT(!m_controlFlowScopeStack.last().isFinallyScope());
     m_controlFlowScopeStack.removeLast();
     m_localScopeDepth--;
 }
@@ -4294,9 +4121,11 @@ bool BytecodeGenerator::emitReadOnlyExceptionIfNeeded(const Variable& variable)
     }
     return false;
 }
-    
+
 void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, ExpressionNode* subjectNode, const std::function<void(BytecodeGenerator&, RegisterID*)>& callBack, ForOfNode* forLoopNode, RegisterID* forLoopSymbolTable)
 {
+    CompletionRecordScope completionRecordScope(*this);
+
     RefPtr<RegisterID> subject = newTemporary();
     emitNode(subject.get(), subjectNode);
     RefPtr<RegisterID> iterator = emitGetById(newTemporary(), subject.get(), propertyNames().iteratorSymbol);
@@ -4307,8 +4136,15 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
     }
 
     RefPtr<Label> loopDone = newLabel();
+    RefPtr<Label> tryStartLabel = newLabel();
+    RefPtr<Label> finallyViaThrowLabel = newLabel();
+    RefPtr<Label> finallyLabel = newLabel();
+    RefPtr<Label> catchLabel = newLabel();
+    RefPtr<Label> endCatchLabel = newLabel();
+
     // RefPtr<Register> iterator's lifetime must be longer than IteratorCloseContext.
-    pushIteratorCloseControlFlowScope(iterator.get(), node);
+    FinallyContext* finallyContext = pushFinallyControlFlowScope(finallyLabel.get());
+
     {
         LabelScopePtr scope = newLabelScope(LabelScope::Loop);
         RefPtr<RegisterID> value = newTemporary();
@@ -4320,40 +4156,75 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
         emitLabel(loopStart.get());
         emitLoopHint();
 
-        RefPtr<Label> tryStartLabel = newLabel();
         emitLabel(tryStartLabel.get());
-        TryData* tryData = pushTry(tryStartLabel.get());
+        TryData* tryData = pushTry(tryStartLabel.get(), finallyViaThrowLabel.get(), HandlerType::SynthesizedFinally);
         callBack(*this, value.get());
         emitJump(scope->continueTarget());
 
-        // IteratorClose sequence for throw-ed control flow.
+        // IteratorClose sequence for abrupt completions.
         {
-            RefPtr<Label> catchHere = emitLabel(newLabel().get());
-            RefPtr<RegisterID> exceptionRegister = newTemporary();
-            RefPtr<RegisterID> thrownValueRegister = newTemporary();
-            popTryAndEmitCatch(tryData, exceptionRegister.get(),
-                thrownValueRegister.get(), catchHere.get(), HandlerType::SynthesizedFinally);
+            // Finally block for the enumeration.
+            emitLabel(finallyViaThrowLabel.get());
+            popTry(tryData, finallyViaThrowLabel.get());
+
+            RefPtr<Label> finallyBodyLabel = newLabel();
+            RefPtr<RegisterID> finallyExceptionRegister = newTemporary();
+            RegisterID* unused = newTemporary();
+
+            emitCatch(completionValueRegister(), unused);
+            emitSetCompletionType(CompletionType::Throw);
+            emitMove(finallyExceptionRegister.get(), completionValueRegister());
+            emitJump(finallyBodyLabel.get());
+
+            emitLabel(finallyLabel.get());
+            emitMoveEmptyValue(finallyExceptionRegister.get());
 
-            RefPtr<Label> catchDone = newLabel();
+            emitLabel(finallyBodyLabel.get());
+            restoreScopeRegister();
+
+            RefPtr<Label> finallyDone = newLabel();
 
             RefPtr<RegisterID> returnMethod = emitGetById(newTemporary(), iterator.get(), propertyNames().returnKeyword);
-            emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), catchDone.get());
+            emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), finallyDone.get());
 
             RefPtr<Label> returnCallTryStart = newLabel();
             emitLabel(returnCallTryStart.get());
-            TryData* returnCallTryData = pushTry(returnCallTryStart.get());
+            TryData* returnCallTryData = pushTry(returnCallTryStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
 
             CallArguments returnArguments(*this, nullptr);
             emitMove(returnArguments.thisRegister(), iterator.get());
             emitCall(value.get(), returnMethod.get(), NoExpectedFunction, returnArguments, node->divot(), node->divotStart(), node->divotEnd(), DebuggableCall::No);
+            emitJumpIfTrue(emitIsObject(newTemporary(), value.get()), finallyDone.get());
+            emitThrowTypeError(ASCIILiteral("Iterator result interface is not an object."));
+
+            emitLabel(finallyDone.get());
+            emitFinallyCompletion(*finallyContext, completionTypeRegister(), endCatchLabel.get());
 
-            emitLabel(catchDone.get());
-            emitThrow(exceptionRegister.get());
+            popTry(returnCallTryData, finallyDone.get());
 
-            // Absorb exception.
-            popTryAndEmitCatch(returnCallTryData, newTemporary(),
-                newTemporary(), catchDone.get(), HandlerType::SynthesizedFinally);
-            emitThrow(exceptionRegister.get());
+            // Catch block for exceptions that may be thrown while calling the return
+            // handler in the enumeration finally block. The only reason we need this
+            // catch block is because if entered the above finally block due to a thrown
+            // exception, then we want to re-throw the original exception on exiting
+            // the finally block. Otherwise, we'll let any new exception pass through.
+            {
+                emitLabel(catchLabel.get());
+                RefPtr<RegisterID> exceptionRegister = newTemporary();
+                RegisterID* unused = newTemporary();
+                emitCatch(exceptionRegister.get(), unused);
+                // Since this is a synthesized catch block and we're guaranteed to never need
+                // to resolve any symbols from the scope, we can skip restoring the scope
+                // register here.
+
+                RefPtr<Label> throwLabel = newLabel();
+                emitJumpIfTrue(emitIsEmpty(newTemporary(), finallyExceptionRegister.get()), throwLabel.get());
+                emitMove(exceptionRegister.get(), finallyExceptionRegister.get());
+
+                emitLabel(throwLabel.get());
+                emitThrow(exceptionRegister.get());
+
+                emitLabel(endCatchLabel.get());
+            }
         }
 
         emitLabel(scope->continueTarget());
@@ -4374,7 +4245,7 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
     }
 
     // IteratorClose sequence for break-ed control flow.
-    popIteratorCloseControlFlowScope();
+    popFinallyControlFlowScope();
     emitIteratorClose(iterator.get(), node);
     emitLabel(loopDone.get());
 }
@@ -4495,6 +4366,14 @@ RegisterID* BytecodeGenerator::emitIsObject(RegisterID* dst, RegisterID* src)
     return dst;
 }
 
+RegisterID* BytecodeGenerator::emitIsNumber(RegisterID* dst, RegisterID* src)
+{
+    emitOpcode(op_is_number);
+    instructions().append(dst->index());
+    instructions().append(src->index());
+    return dst;
+}
+
 RegisterID* BytecodeGenerator::emitIsUndefined(RegisterID* dst, RegisterID* src)
 {
     emitOpcode(op_is_undefined);
@@ -4782,11 +4661,9 @@ RegisterID* BytecodeGenerator::emitYield(RegisterID* argument)
     // Return.
     {
         RefPtr<RegisterID> returnRegister = generatorValueRegister();
-        if (isInFinallyBlock()) {
-            returnRegister = emitMove(newTemporary(), returnRegister.get());
-            emitPopScopes(scopeRegister(), 0);
-        }
-        emitReturn(returnRegister.get());
+        bool hasFinally = emitReturnViaFinallyIfNeeded(returnRegister.get());
+        if (!hasFinally)
+            emitReturn(returnRegister.get());
     }
 
     // Throw.
@@ -4889,9 +4766,9 @@ RegisterID* BytecodeGenerator::emitDelegateYield(RegisterID* argument, Throwable
                     emitGetById(value.get(), value.get(), propertyNames().value);
 
                     emitLabel(returnSequence.get());
-                    if (isInFinallyBlock())
-                        emitPopScopes(scopeRegister(), 0);
-                    emitReturn(value.get());
+                    bool hasFinally = emitReturnViaFinallyIfNeeded(value.get());
+                    if (!hasFinally)
+                        emitReturn(value.get());
                 }
 
                 // Normal.
@@ -4921,6 +4798,144 @@ void BytecodeGenerator::emitGeneratorStateChange(int32_t state)
     emitPutById(generatorRegister(), propertyNames().builtinNames().generatorStatePrivateName(), completedState);
 }
 
+bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget)
+{
+    ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
+    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+    ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
+    if (!scopeDelta)
+        return false; // No finallys to thread through.
+
+    ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
+    ControlFlowScope* bottomScope = &m_controlFlowScopeStack.last() - scopeDelta;
+
+    FinallyContext* innermostFinallyContext = nullptr;
+    FinallyContext* outermostFinallyContext = nullptr;
+    while (topScope > bottomScope) {
+        if (topScope->isFinallyScope()) {
+            FinallyContext* finallyContext = &topScope->finallyContext;
+            if (!innermostFinallyContext)
+                innermostFinallyContext = finallyContext;
+            outermostFinallyContext = finallyContext;
+            finallyContext->incNumberOfBreaksOrContinues();
+        }
+        --topScope;
+    }
+    if (!outermostFinallyContext)
+        return false; // No finallys to thread through.
+
+    auto jumpID = bytecodeOffsetToJumpID(instructions().size());
+    int lexicalScopeIndex = labelScopeDepthToLexicalScopeIndex(targetLabelScopeDepth);
+    outermostFinallyContext->registerJump(jumpID, lexicalScopeIndex, jumpTarget);
+
+    emitSetCompletionType(jumpID);
+    emitJump(innermostFinallyContext->finallyLabel());
+    return true; // We'll be jumping to a finally block.
+}
+
+bool BytecodeGenerator::emitReturnViaFinallyIfNeeded(RegisterID* returnRegister)
+{
+    if (!m_controlFlowScopeStack.size())
+        return false; // No finallys to thread through.
+
+    ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
+    ControlFlowScope* bottomScope = &m_controlFlowScopeStack.first();
+
+    FinallyContext* innermostFinallyContext = nullptr;
+    while (topScope >= bottomScope) {
+        if (topScope->isFinallyScope()) {
+            FinallyContext* finallyContext = &topScope->finallyContext;
+            if (!innermostFinallyContext)
+                innermostFinallyContext = finallyContext;
+            finallyContext->setHandlesReturns();
+        }
+        --topScope;
+    }
+    if (!innermostFinallyContext)
+        return false; // No finallys to thread through.
+
+    emitSetCompletionType(CompletionType::Return);
+    emitSetCompletionValue(returnRegister);
+    emitJump(innermostFinallyContext->finallyLabel());
+    return true; // We'll be jumping to a finally block.
+}
+
+void BytecodeGenerator::emitFinallyCompletion(FinallyContext& context, RegisterID* completionTypeRegister, Label* normalCompletionLabel)
+{
+    if (context.numberOfBreaksOrContinues() || context.handlesReturns()) {
+        emitJumpIf(op_stricteq, completionTypeRegister, CompletionType::Normal, normalCompletionLabel);
+
+        FinallyContext* outerContext = context.outerContext();
+
+        size_t numberOfJumps = context.numberOfJumps();
+        ASSERT(outerContext || numberOfJumps == context.numberOfBreaksOrContinues());
+
+        for (size_t i = 0; i < numberOfJumps; i++) {
+            RefPtr<Label> nextLabel = newLabel();
+            auto& jump = context.jumps(i);
+            emitJumpIf(op_nstricteq, completionTypeRegister, jump.jumpID, nextLabel.get());
+
+            restoreScopeRegister(jump.targetLexicalScopeIndex);
+            emitSetCompletionType(CompletionType::Normal);
+            emitJump(jump.targetLabel.get());
+
+            emitLabel(nextLabel.get());
+        }
+
+        if (outerContext) {
+            // We are not the outermost finally.
+            bool hasBreaksOrContinuesNotCoveredByJumps = context.numberOfBreaksOrContinues() > numberOfJumps;
+            if (hasBreaksOrContinuesNotCoveredByJumps || context.handlesReturns())
+                emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Throw, outerContext->finallyLabel());
+
+        } else {
+            // We are the outermost finally.
+            if (context.handlesReturns()) {
+                RefPtr<Label> notReturnLabel = newLabel();
+                emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Return, notReturnLabel.get());
+
+                emitWillLeaveCallFrameDebugHook();
+                emitReturn(completionValueRegister(), ReturnFrom::Finally);
+                
+                emitLabel(notReturnLabel.get());
+            }
+        }
+    }
+    emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Throw, normalCompletionLabel);
+    emitThrow(completionValueRegister());
+}
+
+bool BytecodeGenerator::allocateCompletionRecordRegisters()
+{
+    if (m_completionTypeRegister)
+        return false;
+
+    ASSERT(!m_completionValueRegister);
+    m_completionTypeRegister = newTemporary();
+    m_completionValueRegister = newTemporary();
+
+    emitSetCompletionType(CompletionType::Normal);
+    emitMoveEmptyValue(m_completionValueRegister.get());
+    return true;
+}
+
+void BytecodeGenerator::releaseCompletionRecordRegisters()
+{
+    ASSERT(m_completionTypeRegister && m_completionValueRegister);
+    m_completionTypeRegister = nullptr;
+    m_completionValueRegister = nullptr;
+}
+
+void BytecodeGenerator::emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType type, Label* jumpTarget)
+{
+    RefPtr<RegisterID> tempRegister = newTemporary();
+    RegisterID* valueConstant = addConstantValue(jsNumber(static_cast<int>(type)));
+    OperandTypes operandTypes = OperandTypes(ResultType::numberTypeIsInt32(), ResultType::unknownType());
+
+    auto equivalenceResult = emitBinaryOp(compareOpcode, tempRegister.get(), valueConstant, completionTypeRegister, operandTypes);
+    emitJumpIfTrue(equivalenceResult, jumpTarget);
+}
+
 } // namespace JSC
 
 namespace WTF {
index 961af71..6aef19b 100644 (file)
@@ -44,6 +44,7 @@
 #include "TemplateRegistryKey.h"
 #include "UnlinkedCodeBlock.h"
 #include <functional>
+#include <wtf/CheckedArithmetic.h>
 #include <wtf/HashTraits.h>
 #include <wtf/PassRefPtr.h>
 #include <wtf/SegmentedVector.h>
@@ -80,22 +81,99 @@ namespace JSC {
         unsigned m_padding;
     };
 
+    // https://tc39.github.io/ecma262/#sec-completion-record-specification-type
+    //
+    // For the Break and Continue cases, instead of using the Break and Continue enum values
+    // below, we use the unique jumpID of the break and continue statement as the encoding
+    // for the CompletionType value. emitFinallyCompletion() uses this jumpID value later
+    // to determine the appropriate jump target to jump to after executing the relevant finally
+    // blocks. The jumpID is computed as:
+    //     jumpID = bytecodeOffset (of the break/continue node) + CompletionType::NumberOfTypes.
+    // Hence, there won't be any collision between jumpIDs and CompletionType enums.
+    enum class CompletionType : int {
+        Normal,
+        Break,
+        Continue,
+        Return,
+        Throw,
+        
+        NumberOfTypes
+    };
+
+    inline CompletionType bytecodeOffsetToJumpID(unsigned offset)
+    {
+        int jumpIDAsInt = offset + static_cast<int>(CompletionType::NumberOfTypes);
+        ASSERT(jumpIDAsInt >= static_cast<int>(CompletionType::NumberOfTypes));
+        return static_cast<CompletionType>(jumpIDAsInt);
+    }
+
+    struct FinallyJump {
+        FinallyJump(CompletionType jumpID, int targetLexicalScopeIndex, Label* targetLabel)
+            : jumpID(jumpID)
+            , targetLexicalScopeIndex(targetLexicalScopeIndex)
+            , targetLabel(targetLabel)
+        { }
+
+        CompletionType jumpID;
+        int targetLexicalScopeIndex;
+        RefPtr<Label> targetLabel;
+    };
+
     struct FinallyContext {
-        StatementNode* finallyBlock;
-        RegisterID* iterator;
-        ThrowableExpressionData* enumerationNode;
-        unsigned controlFlowScopeStackSize;
-        unsigned switchContextStackSize;
-        unsigned forInContextStackSize;
-        unsigned tryContextStackSize;
-        unsigned labelScopesSize;
-        unsigned lexicalScopeStackSize;
-        int finallyDepth;
-        int dynamicScopeDepth;
+        FinallyContext() { }
+        FinallyContext(FinallyContext* outerContext, Label* finallyLabel, int finallyDepth)
+            : m_outerContext(outerContext)
+            , m_finallyLabel(finallyLabel)
+            , m_finallyDepth(finallyDepth)
+        {
+            ASSERT(m_finallyDepth >= 0);
+            ASSERT(m_jumps.isEmpty());
+        }
+
+        FinallyContext* outerContext() const { return m_outerContext; }
+        Label* finallyLabel() const { return m_finallyLabel; }
+        int depth() const { return m_finallyDepth; }
+
+        uint32_t numberOfBreaksOrContinues() const { return m_numberOfBreaksOrContinues.unsafeGet(); }
+        void incNumberOfBreaksOrContinues() { m_numberOfBreaksOrContinues++; }
+
+        bool handlesReturns() const { return m_handlesReturns; }
+        void setHandlesReturns() { m_handlesReturns = true; }
+
+        void registerJump(CompletionType jumpID, int lexicalScopeIndex, Label* targetLabel)
+        {
+            m_jumps.append(FinallyJump(jumpID, lexicalScopeIndex, targetLabel));
+        }
+
+        size_t numberOfJumps() const { return m_jumps.size(); }
+        FinallyJump& jumps(size_t i) { return m_jumps[i]; }
+
+    private:
+        FinallyContext* m_outerContext { nullptr };
+        Label* m_finallyLabel { nullptr };
+        int m_finallyDepth { 0 };
+        Checked<uint32_t, WTF::CrashOnOverflow> m_numberOfBreaksOrContinues;
+        bool m_handlesReturns { false };
+        Vector<FinallyJump> m_jumps;
     };
 
     struct ControlFlowScope {
-        bool isFinallyBlock;
+        typedef uint8_t Type;
+        enum {
+            Label,
+            Finally
+        };
+        ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext&& finallyContext = FinallyContext())
+            : type(type)
+            , lexicalScopeIndex(lexicalScopeIndex)
+            , finallyContext(std::forward<FinallyContext>(finallyContext))
+        { }
+
+        bool isLabelScope() const { return type == Label; }
+        bool isFinallyScope() const { return type == Finally; }
+
+        Type type;
+        int lexicalScopeIndex;
         FinallyContext finallyContext;
     };
 
@@ -605,7 +683,8 @@ namespace JSC {
 
         RegisterID* emitGetTemplateObject(RegisterID* dst, TaggedTemplateNode*);
 
-        RegisterID* emitReturn(RegisterID* src);
+        enum class ReturnFrom { Normal, Finally };
+        RegisterID* emitReturn(RegisterID* src, ReturnFrom = ReturnFrom::Normal);
         RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
 
         RegisterID* emitConstruct(RegisterID* dst, RegisterID* func, ExpectedFunction, CallArguments&, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
@@ -626,7 +705,6 @@ namespace JSC {
         PassRefPtr<Label> emitJumpIfFalse(RegisterID* cond, Label* target);
         PassRefPtr<Label> emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
         PassRefPtr<Label> emitJumpIfNotFunctionApply(RegisterID* cond, Label* target);
-        void emitPopScopes(RegisterID* srcDst, int targetScopeDepth);
 
         void emitEnter();
         void emitWatchdog();
@@ -649,6 +727,7 @@ namespace JSC {
         RegisterID* emitIsMap(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSMapType); }
         RegisterID* emitIsSet(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSSetType); }
         RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
+        RegisterID* emitIsNumber(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsEmpty(RegisterID* dst, RegisterID* src);
         RegisterID* emitIsDerivedArray(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, DerivedArrayType); }
@@ -663,9 +742,30 @@ namespace JSC {
         bool emitReadOnlyExceptionIfNeeded(const Variable&);
 
         // Start a try block. 'start' must have been emitted.
-        TryData* pushTry(Label* start);
+        TryData* pushTry(Label* start, Label* handlerLabel, HandlerType);
         // End a try block. 'end' must have been emitted.
-        void popTryAndEmitCatch(TryData*, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType);
+        void popTry(TryData*, Label* end);
+        void emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister);
+
+    private:
+        static const int CurrentLexicalScopeIndex = -2;
+        static const int OutermostLexicalScopeIndex = -1;
+
+        int currentLexicalScopeIndex() const
+        {
+            int size = static_cast<int>(m_lexicalScopeStack.size());
+            ASSERT(static_cast<size_t>(size) == m_lexicalScopeStack.size());
+            ASSERT(size >= 0);
+            if (!size)
+                return OutermostLexicalScopeIndex;
+            return size - 1;
+        }
+
+    public:
+        void restoreScopeRegister();
+        void restoreScopeRegister(int lexicalScopeIndex);
+
+        int labelScopeDepthToLexicalScopeIndex(int labelScopeDepth);
 
         void emitThrow(RegisterID* exc)
         { 
@@ -698,12 +798,58 @@ namespace JSC {
         void emitDebugHook(ExpressionNode*);
         void emitWillLeaveCallFrameDebugHook();
 
-        bool isInFinallyBlock() { return m_finallyDepth > 0; }
+        class CompletionRecordScope {
+        public:
+            CompletionRecordScope(BytecodeGenerator& generator, bool needCompletionRecordRegisters = true)
+                : m_generator(generator)
+            {
+                if (needCompletionRecordRegisters && m_generator.allocateCompletionRecordRegisters())
+                    m_needToReleaseOnDestruction = true;
+            }
+            ~CompletionRecordScope()
+            {
+                if (m_needToReleaseOnDestruction)
+                    m_generator.releaseCompletionRecordRegisters();
+            }
+
+        private:
+            BytecodeGenerator& m_generator;
+            bool m_needToReleaseOnDestruction { false };
+        };
+
+        RegisterID* completionTypeRegister() const
+        {
+            ASSERT(m_completionTypeRegister);
+            return m_completionTypeRegister.get();
+        }
+        RegisterID* completionValueRegister() const
+        {
+            ASSERT(m_completionValueRegister);
+            return m_completionValueRegister.get();
+        }
+
+        void emitSetCompletionType(CompletionType type)
+        {
+            emitLoad(completionTypeRegister(), JSValue(static_cast<int>(type)));
+        }
+        void emitSetCompletionValue(RegisterID* reg)
+        {
+            emitMove(completionValueRegister(), reg);
+        }
+
+        void emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType, Label* jumpTarget);
 
-        void pushFinallyControlFlowScope(StatementNode* finallyBlock);
-        void popFinallyControlFlowScope();
-        void pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* enumerationNode);
-        void popIteratorCloseControlFlowScope();
+        bool emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget);
+        bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
+        void emitFinallyCompletion(FinallyContext&, RegisterID* completionTypeRegister, Label* normalCompletionLabel);
+
+    private:
+        bool allocateCompletionRecordRegisters();
+        void releaseCompletionRecordRegisters();
+
+    public:
+        FinallyContext* pushFinallyControlFlowScope(Label* finallyLabel);
+        FinallyContext popFinallyControlFlowScope();
 
         void pushIndexedForInScope(RegisterID* local, RegisterID* index);
         void popIndexedForInScope(RegisterID* local);
@@ -797,7 +943,6 @@ namespace JSC {
 
         void allocateCalleeSaveSpace();
         void allocateAndEmitScope();
-        void emitComplexPopScopes(RegisterID*, ControlFlowScope* topScope, ControlFlowScope* bottomScope);
 
         typedef HashMap<double, JSValue> NumberMap;
         typedef HashMap<UniquedStringImpl*, JSString*, IdentifierRepHash> IdentifierStringMap;
@@ -935,6 +1080,11 @@ namespace JSC {
         RegisterID* m_arrowFunctionContextLexicalEnvironmentRegister { nullptr };
         RegisterID* m_promiseCapabilityRegister { nullptr };
 
+        RefPtr<RegisterID> m_completionTypeRegister;
+        RefPtr<RegisterID> m_completionValueRegister;
+
+        FinallyContext* m_currentFinallyContext { nullptr };
+
         SegmentedVector<RegisterID*, 16> m_localRegistersForCalleeSaveRegisters;
         SegmentedVector<RegisterID, 32> m_constantPoolRegisters;
         SegmentedVector<RegisterID, 32> m_calleeLocals;
@@ -949,7 +1099,9 @@ namespace JSC {
         void pushLocalControlFlowScope();
         void popLocalControlFlowScope();
 
-        Vector<ControlFlowScope, 0, UnsafeVectorOverflow> m_controlFlowScopeStack;
+        // FIXME: Restore overflow checking with UnsafeVectorOverflow once SegmentVector supports it.
+        // https://bugs.webkit.org/show_bug.cgi?id=165980
+        SegmentedVector<ControlFlowScope, 16> m_controlFlowScopeStack;
         Vector<SwitchInfo> m_switchContextStack;
         Vector<RefPtr<ForInContext>> m_forInContextStack;
         Vector<TryContext> m_tryContextStack;
index 6aa4fb3..7becf75 100644 (file)
@@ -2998,8 +2998,12 @@ void ContinueNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
     LabelScopePtr scope = generator.continueTarget(m_ident);
     ASSERT(scope);
 
-    generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
-    generator.emitJump(scope->continueTarget());
+    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->continueTarget());
+    if (!hasFinally) {
+        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
+        generator.restoreScopeRegister(lexicalScopeIndex);
+        generator.emitJump(scope->continueTarget());
+    }
 
     generator.emitProfileControlFlow(endOffset());
 }
@@ -3025,8 +3029,12 @@ void BreakNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
     LabelScopePtr scope = generator.breakTarget(m_ident);
     ASSERT(scope);
 
-    generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
-    generator.emitJump(scope->breakTarget());
+    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->breakTarget());
+    if (!hasFinally) {
+        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
+        generator.restoreScopeRegister(lexicalScopeIndex);
+        generator.emitJump(scope->breakTarget());
+    }
 
     generator.emitProfileControlFlow(endOffset());
 }
@@ -3043,14 +3051,14 @@ void ReturnNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
     RefPtr<RegisterID> returnRegister = m_value ? generator.emitNodeInTailPosition(dst, m_value) : generator.emitLoad(dst, jsUndefined());
 
     generator.emitProfileType(returnRegister.get(), ProfileTypeBytecodeFunctionReturnStatement, divotStart(), divotEnd());
-    if (generator.isInFinallyBlock()) {
-        returnRegister = generator.emitMove(generator.newTemporary(), returnRegister.get());
-        generator.emitPopScopes(generator.scopeRegister(), 0);
+
+    bool hasFinally = generator.emitReturnViaFinallyIfNeeded(returnRegister.get());
+    if (!hasFinally) {
+        generator.emitWillLeaveCallFrameDebugHook();
+        generator.emitReturn(returnRegister.get());
     }
 
-    generator.emitWillLeaveCallFrameDebugHook();
-    generator.emitReturn(returnRegister.get());
-    generator.emitProfileControlFlow(endOffset()); 
+    generator.emitProfileControlFlow(endOffset());
     // Emitting an unreachable return here is needed in case this op_profile_control_flow is the 
     // last opcode in a CodeBlock because a CodeBlock's instructions must end with a terminal opcode.
     if (generator.vm()->controlFlowProfiler())
@@ -3279,32 +3287,56 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
     // optimizer knows they may be jumped to from anywhere.
 
     ASSERT(m_catchBlock || m_finallyBlock);
+    BytecodeGenerator::CompletionRecordScope completionRecordScope(generator, m_finallyBlock);
+
+    RefPtr<Label> catchLabel;
+    RefPtr<Label> catchEndLabel;
+    RefPtr<Label> finallyViaThrowLabel;
+    RefPtr<Label> finallyLabel;
+    RefPtr<Label> finallyEndLabel;
 
     RefPtr<Label> tryStartLabel = generator.newLabel();
     generator.emitLabel(tryStartLabel.get());
-    
-    if (m_finallyBlock)
-        generator.pushFinallyControlFlowScope(m_finallyBlock);
-    TryData* tryData = generator.pushTry(tryStartLabel.get());
 
-    generator.emitNode(dst, m_tryBlock);
+    if (m_finallyBlock) {
+        finallyViaThrowLabel = generator.newLabel();
+        finallyLabel = generator.newLabel();
+        finallyEndLabel = generator.newLabel();
 
+        generator.pushFinallyControlFlowScope(finallyLabel.get());
+    }
     if (m_catchBlock) {
-        RefPtr<Label> catchEndLabel = generator.newLabel();
-        
-        // Normal path: jump over the catch block.
+        catchLabel = generator.newLabel();
+        catchEndLabel = generator.newLabel();
+    }
+
+    Label* tryHandlerLabel = m_catchBlock ? catchLabel.get() : finallyViaThrowLabel.get();
+    HandlerType tryHandlerType = m_catchBlock ? HandlerType::Catch : HandlerType::Finally;
+    TryData* tryData = generator.pushTry(tryStartLabel.get(), tryHandlerLabel, tryHandlerType);
+
+    generator.emitNode(dst, m_tryBlock);
+
+    if (m_finallyBlock)
+        generator.emitJump(finallyLabel.get());
+    else
         generator.emitJump(catchEndLabel.get());
 
+    RefPtr<Label> endTryLabel = generator.emitLabel(generator.newLabel().get());
+    generator.popTry(tryData, endTryLabel.get());
+
+    if (m_catchBlock) {
         // Uncaught exception path: the catch block.
-        RefPtr<Label> here = generator.emitLabel(generator.newLabel().get());
-        RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
+        generator.emitLabel(catchLabel.get());
         RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
-        generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), here.get(), HandlerType::Catch);
-        
+        RegisterID* unused = generator.newTemporary();
+        generator.emitCatch(unused, thrownValueRegister.get());
+        generator.restoreScopeRegister();
+
+        TryData* tryData = nullptr;
         if (m_finallyBlock) {
             // If the catch block throws an exception and we have a finally block, then the finally
             // block should "catch" that exception.
-            tryData = generator.pushTry(here.get());
+            tryData = generator.pushTry(catchLabel.get(), finallyViaThrowLabel.get(), HandlerType::Finally);
         }
 
         generator.emitPushCatchScope(m_lexicalVariables);
@@ -3316,37 +3348,41 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
             generator.emitNodeInTailPosition(dst, m_catchBlock);
         generator.emitLoad(thrownValueRegister.get(), jsUndefined());
         generator.emitPopCatchScope(m_lexicalVariables);
+
+        if (m_finallyBlock) {
+            generator.emitSetCompletionType(CompletionType::Normal);
+            generator.emitJump(finallyLabel.get());
+            generator.popTry(tryData, finallyViaThrowLabel.get());
+        }
+
         generator.emitLabel(catchEndLabel.get());
+        generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
     }
 
     if (m_finallyBlock) {
-        RefPtr<Label> preFinallyLabel = generator.emitLabel(generator.newLabel().get());
-        
-        generator.popFinallyControlFlowScope();
+        FinallyContext finallyContext = generator.popFinallyControlFlowScope();
 
-        RefPtr<Label> finallyEndLabel = generator.newLabel();
+        // Entry to the finally block for CompletionType::Throw.
+        generator.emitLabel(finallyViaThrowLabel.get());
+        RegisterID* unused = generator.newTemporary();
+        generator.emitCatch(generator.completionValueRegister(), unused);
+        generator.emitSetCompletionType(CompletionType::Throw);
 
-        int finallyStartOffset = m_catchBlock ? m_catchBlock->endOffset() + 1 : m_tryBlock->endOffset() + 1;
+        // Entry to the finally block for CompletionTypes other than Throw.
+        generator.emitLabel(finallyLabel.get());
+        generator.restoreScopeRegister();
 
-        // Normal path: run the finally code, and jump to the end.
-        generator.emitProfileControlFlow(finallyStartOffset);
-        generator.emitNodeInTailPosition(dst, m_finallyBlock);
-        generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
-        generator.emitJump(finallyEndLabel.get());
+        RefPtr<RegisterID> savedCompletionTypeRegister = generator.newTemporary();
+        generator.emitMove(savedCompletionTypeRegister.get(), generator.completionTypeRegister());
 
-        // Uncaught exception path: invoke the finally block, then re-throw the exception.
-        RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
-        RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
-        generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), preFinallyLabel.get(), HandlerType::Finally);
+        int finallyStartOffset = m_catchBlock ? m_catchBlock->endOffset() + 1 : m_tryBlock->endOffset() + 1;
         generator.emitProfileControlFlow(finallyStartOffset);
         generator.emitNodeInTailPosition(dst, m_finallyBlock);
-        generator.emitThrow(exceptionRegister.get());
 
+        generator.emitFinallyCompletion(finallyContext, savedCompletionTypeRegister.get(), finallyEndLabel.get());
         generator.emitLabel(finallyEndLabel.get());
         generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
-    } else
-        generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
-
+    }
 }
 
 // ------------------------------ ScopeNode -----------------------------
index 6ebda3c..b71ce0f 100644 (file)
@@ -1,3 +1,17 @@
+2016-12-22  Mark Lam  <mark.lam@apple.com>
+
+        De-duplicate finally blocks.
+        https://bugs.webkit.org/show_bug.cgi?id=160168
+
+        Reviewed by Saam Barati.
+
+        Added some methods to bring SegmentedVector closer to parity with Vector.
+
+        * wtf/SegmentedVector.h:
+        (WTF::SegmentedVector::first):
+        (WTF::SegmentedVector::last):
+        (WTF::SegmentedVector::takeLast):
+
 2016-12-19  Mark Lam  <mark.lam@apple.com>
 
         Rolling out r209974 and r209952. They break some websites in mysterious ways. Step 2: Rollout r209952.
index c11bde7..3e3fe00 100644 (file)
@@ -127,10 +127,34 @@ namespace WTF {
             return at(index);
         }
 
+        T& first()
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(0);
+        }
+        const T& first() const
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(0);
+        }
         T& last()
         {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
             return at(size() - 1);
         }
+        const T& last() const
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(size() - 1);
+        }
+
+        T takeLast()
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            T result = WTFMove(last());
+            --m_size;
+            return result;
+        }
 
         template<typename... Args>
         void append(Args&&... args)