<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[210116] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/210116">210116</a></dd>
<dt>Author</dt> <dd>mark.lam@apple.com</dd>
<dt>Date</dt> <dd>2016-12-22 14:48:32 -0800 (Thu, 22 Dec 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>De-duplicate finally blocks.
https://bugs.webkit.org/show_bug.cgi?id=160168

Reviewed by Saam Barati.

JSTests:

Re-landing <a href="http://trac.webkit.org/projects/webkit/changeset/209952">r209952</a> 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 &quot;CompletionType::Throw&quot; 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 &quot;<a href="http://trac.webkit.org/projects/webkit/changeset/1">r1</a>&quot;; // 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 &quot;<a href="http://trac.webkit.org/projects/webkit/changeset/1">r1</a>&quot;; // 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 &quot;<a href="http://trac.webkit.org/projects/webkit/changeset/1">r1</a>&quot;.
           }

           ...
       }

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):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkJSTestsChangeLog">trunk/JSTests/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCoreChangeLog">trunk/Source/JavaScriptCore/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecodeHandlerInfoh">trunk/Source/JavaScriptCore/bytecode/HandlerInfo.h</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecompilerBytecodeGeneratorcpp">trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecompilerBytecodeGeneratorh">trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h</a></li>
<li><a href="#trunkSourceJavaScriptCorebytecompilerNodesCodegencpp">trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp</a></li>
<li><a href="#trunkSourceWTFChangeLog">trunk/Source/WTF/ChangeLog</a></li>
<li><a href="#trunkSourceWTFwtfSegmentedVectorh">trunk/Source/WTF/wtf/SegmentedVector.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkJSTestsstressdeeplynestedfinallysjs">trunk/JSTests/stress/deeply-nested-finallys.js</a></li>
<li><a href="#trunkJSTestsstresstestfinallyjs">trunk/JSTests/stress/test-finally.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkJSTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/JSTests/ChangeLog (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/JSTests/ChangeLog        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/JSTests/ChangeLog        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -1,3 +1,20 @@
</span><ins>+2016-12-22  Mark Lam  &lt;mark.lam@apple.com&gt;
+
+        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.
+
</ins><span class="cx"> 2016-12-22  Saam Barati  &lt;sbarati@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         WebAssembly: Make the spec-tests/address.wast.js test pass
</span></span></pre></div>
<a id="trunkJSTestsstressdeeplynestedfinallysjsfromrev210009trunkJSTestsstressdeeplynestedfinallysjs"></a>
<div class="copfile"><h4>Copied: trunk/JSTests/stress/deeply-nested-finallys.js (from rev 210009, trunk/JSTests/stress/deeply-nested-finallys.js) (0 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/JSTests/stress/deeply-nested-finallys.js                                (rev 0)
+++ trunk/JSTests/stress/deeply-nested-finallys.js        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -0,0 +1,78 @@
</span><ins>+// 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();
</ins></span></pre></div>
<a id="trunkJSTestsstresstestfinallyjsfromrev210009trunkJSTestsstresstestfinallyjs"></a>
<div class="copfile"><h4>Copied: trunk/JSTests/stress/test-finally.js (from rev 210009, trunk/JSTests/stress/test-finally.js) (0 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/JSTests/stress/test-finally.js                                (rev 0)
+++ trunk/JSTests/stress/test-finally.js        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -0,0 +1,1445 @@
</span><ins>+// 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 = &quot;NothingReturned&quot;;
+let NothingThrown = &quot;NothingThrown&quot;;
+
+function assertResults(expectedSteps, expectedReturned, expectedThrown) {
+    let stepsMismatch = (steps != expectedSteps);
+    let returnedMismatch = (returned != expectedReturned);
+    let thrownMismatch = (thrown != expectedThrown &amp;&amp; !(&quot;&quot; + thrown).startsWith(&quot;&quot; + expectedThrown));
+
+    if (stepsMismatch || returnedMismatch || thrownMismatch) {
+        print(&quot;FAILED Test @ line &quot; + testLineNumber + &quot;:&quot;);
+        print(&quot;   Actual:   steps: &quot; + steps + &quot;, returned: &quot; + returned + &quot;, thrown: '&quot; + thrown + &quot;'&quot;);
+        print(&quot;   Expected: steps: &quot; + expectedSteps + &quot;, returned: &quot; + expectedReturned + &quot;, thrown: '&quot; + expectedThrown + &quot;'&quot;);
+    }
+    if (stepsMismatch)
+        throw &quot;FAILED Test @ line &quot; + testLineNumber + &quot;: steps do not match: expected ='&quot; + expectedSteps + &quot;' actual='&quot; + steps + &quot;'&quot;;
+    if (returnedMismatch)
+        throw &quot;FAILED Test @ line &quot; + testLineNumber + &quot;: returned value does not match: expected ='&quot; + expectedReturned + &quot;' actual='&quot; + returned + &quot;'&quot;;
+    if (thrownMismatch)
+        throw &quot;FAILED Test @ line &quot; + testLineNumber + &quot;: thrown value does does not match: expected ='&quot; + expectedThrown + &quot;' actual='&quot; + thrown + &quot;'&quot;;
+}
+
+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(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,f1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Normal.
+test(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+                append(&quot;t1&quot;);
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Break.
+test(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+                append(&quot;t1&quot;);
+                break;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Continue.
+test(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+                append(&quot;t1&quot;);
+                continue;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Return.
+test(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+                append(&quot;t1&quot;);
+                return;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Throw.
+test(() =&gt;  {
+    try {
+        append(&quot;t2&quot;);
+        for (var i = 0; i &lt; 2; i++) {
+            try {
+                append(&quot;t1&quot;);
+                throw { };
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,c1,f1,t1,c1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Normal in a for-of loop.
+test(() =&gt;  {
+    let arr = [1, 2];
+    try {
+        append(&quot;t2&quot;);
+        for (let x of arr) {
+            try {
+                append(&quot;t1&quot;);
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Break in a for-of loop.
+test(() =&gt;  {
+    let arr = [1, 2];
+    try {
+        append(&quot;t2&quot;);
+        for (let x of arr) {
+            try {
+                append(&quot;t1&quot;);
+                break;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Continue in a for-of loop.
+test(() =&gt;  {
+    let arr = [1, 2];
+    try {
+        append(&quot;t2&quot;);
+        for (let x of arr) {
+            try {
+                append(&quot;t1&quot;);
+                continue;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Return in a for-of loop.
+test(() =&gt;  {
+    let arr = [1, 2];
+    try {
+        append(&quot;t2&quot;);
+        for (let x of arr) {
+            try {
+                append(&quot;t1&quot;);
+                return;
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,f1,f2&quot;, undefined, NothingThrown);
+
+// Test CompletionType::Throw in a for-of loop.
+test(() =&gt;  {
+    let arr = [1, 2];
+    try {
+        append(&quot;t2&quot;);
+        for (let x of arr) {
+            try {
+                append(&quot;t1&quot;);
+                throw { };
+            } catch(a) {
+                append(&quot;c1&quot;);
+            } finally {
+                append(&quot;f1&quot;);
+            }
+        }
+    } catch(b) {
+        append(&quot;c2&quot;);
+    } finally {
+        append(&quot;f2&quot;);
+    }
+
+}, &quot;t2,t1,c1,f1,t1,c1,f1,f2&quot;, undefined, NothingThrown);
+
+
+// No abrupt completions.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+    } finally {
+        append(&quot;c&quot;);
+    }   
+    append(&quot;d&quot;);
+    return 400;
+
+}, &quot;a,b,c,d&quot;, 400, NothingThrown);
+
+
+// Return after a. Should not execute any finally blocks, and return a's result.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    return 100;
+    try {
+        append(&quot;b&quot;);
+        return 200;
+        try {
+            append(&quot;c&quot;);
+            return 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        return 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a&quot;, 100, NothingThrown);
+
+// Return after b. Should execute finally block f only, and return b's result.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        return 200;
+        try {
+            append(&quot;c&quot;);
+            return 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        return 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,f&quot;, 200, NothingThrown);
+
+// Return after c. Should execute finally blocks d and f, and return c's result.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            return 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        return 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,d,f&quot;, 300, NothingThrown);
+
+// Return after c and d. Should execute finally blocks d and f, and return last return value from d.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            return 300;
+        } finally {
+            append(&quot;d&quot;);
+            return 400;
+        }
+        append(&quot;e&quot;);
+        return 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,d,f&quot;, 400, NothingThrown);
+
+// Return after c, d, and f. Should execute finally blocks d and f, and return last return value from f.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            return 300;
+        } finally {
+            append(&quot;d&quot;);
+            return 400;
+        }
+        append(&quot;e&quot;);
+        return 500;
+    } finally {
+        append(&quot;f&quot;);
+        return 600;
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,d,f&quot;, 600, NothingThrown);
+
+// Return after g.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,d,e,f,g&quot;, 700, NothingThrown);
+
+// Throw after a.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    throw 100;
+    try {
+        append(&quot;b&quot;);
+        throw 200;
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    throw 700;
+
+}, &quot;a&quot;, NothingReturned, 100);
+
+// Throw after b.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        throw 200;
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    throw 700;
+
+}, &quot;a,b,f&quot;, NothingReturned, 200);
+
+// Throw after c.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    throw 700;
+
+}, &quot;a,b,c,d,f&quot;, NothingReturned, 300);
+
+// Throw after c and d.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+            throw 400;
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    throw 700;
+
+}, &quot;a,b,c,d,f&quot;, NothingReturned, 400);
+
+// Throw after c, d, and f.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+            throw 400;
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } finally {
+        append(&quot;f&quot;);
+        throw 600;
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,d,f&quot;, NothingReturned, 600);
+
+// Throw after g.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    throw 700;
+
+}, &quot;a,b,c,d,e,f,g&quot;, NothingReturned, 700);
+
+// Throw after b with catch at z.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        throw 200;
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } catch (e) {
+        append(&quot;z&quot;);
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,z,f,g&quot;, 700, NothingThrown);
+
+// Throw after b with catch at z and rethrow at z.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        throw 200;
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+        throw 500;
+    } catch (e) {
+        append(&quot;z&quot;);
+        throw 5000;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,z,f&quot;, NothingReturned, 5000);
+
+// Throw after c with catch at y and z.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } catch (e) {
+            append(&quot;y&quot;);
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } catch (e) {
+        append(&quot;z&quot;);
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,y,d,e,f,g&quot;, 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } catch (e) {
+            append(&quot;y&quot;);
+            throw 3000;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } catch (e) {
+        append(&quot;z&quot;);
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,y,d,z,f,g&quot;, 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y and z.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } catch (e) {
+            append(&quot;y&quot;);
+            throw 3000;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } catch (e) {
+        append(&quot;z&quot;);
+        throw 5000;
+    } finally {
+        append(&quot;f&quot;);
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,y,d,z,f&quot;, NothingReturned, 5000);
+
+// Throw after c with catch at y and z, and rethrow at y, z, and f.
+test(() =&gt; {
+    append(&quot;a&quot;);
+    try {
+        append(&quot;b&quot;);
+        try {
+            append(&quot;c&quot;);
+            throw 300;
+        } catch (e) {
+            append(&quot;y&quot;);
+            throw 3000;
+        } finally {
+            append(&quot;d&quot;);
+        }
+        append(&quot;e&quot;);
+    } catch (e) {
+        append(&quot;z&quot;);
+        throw 5000;
+    } finally {
+        append(&quot;f&quot;);
+        throw 600;
+    }   
+    append(&quot;g&quot;);
+    return 700;
+
+}, &quot;a,b,c,y,d,z,f&quot;, NothingReturned, 600);
+
+// No throw or return in for-of loop.
+test(() =&gt; {
+    class TestIterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,n,2,n,x&quot;, 200, NothingThrown);
+
+// Break in for-of loop.
+test(() =&gt; {
+    class TestIterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r,x&quot;, 200, NothingThrown);
+
+// Break in for-of loop with throw in Iterator.return().
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 300);
+
+// Break in for-of loop with Iterator.return() returning a non object.
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            break;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 'TypeError');
+
+// Return in for-of loop.
+test(() =&gt; {
+    class TestIterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, 100, NothingThrown);
+
+// Return in for-of loop with throw in Iterator.return().
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 300);
+
+// Return in for-of loop with Iterator.return() returning a non object.
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            return 100;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 'TypeError');
+
+
+// Throw in for-of loop.
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            throw 100;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 100);
+
+// Throw in for-of loop with throw in Iterator.return().
+test(() =&gt; {
+    class Iterator {
+        constructor() {
+            append(&quot;c&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;n&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;r&quot;);
+            throw 300;
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new Iterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        if (element == 1)
+            throw 100;
+    }
+    append(&quot;x&quot;);
+    return 200;
+
+}, &quot;c,n,0,n,1,r&quot;, NothingReturned, 100);
+
+// Handling return in finally block F1 with try-finally in F1's body.
+test(() =&gt;  {
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        try {
+            append(&quot;t2&quot;);
+        } catch (e) {
+            append(&quot;c2&quot;);
+        } finally {
+            append(&quot;f2&quot;);
+        }
+        append(&quot;f1b&quot;);
+    }
+
+}, &quot;t1a,f1a,t2,f2,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling return in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        try {
+            append(&quot;t1a&quot;);
+            return &quot;r1&quot;;
+            append(&quot;t1b&quot;);
+        } catch(e) {
+            append(&quot;c1&quot;);
+        } finally {
+            append(&quot;f1a&quot;);
+
+            try {
+                append(&quot;t2&quot;);
+            } catch (e) {
+                append(&quot;c2&quot;);
+            } finally {
+                append(&quot;f2&quot;);
+            }
+            append(&quot;f1b&quot;);
+        }
+    }
+    append(&quot;x&quot;);
+
+}, &quot;ci,ni,0,t1a,f1a,t2,f2,f1b,ri&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling break in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    for (var element of arr) {
+        append(element);
+        try {
+            append(&quot;t1a&quot;);
+            break;
+            append(&quot;t1b&quot;);
+        } catch(e) {
+            append(&quot;c1&quot;);
+        } finally {
+            append(&quot;f1a&quot;);
+
+            try {
+                append(&quot;t2&quot;);
+            } catch (e) {
+                append(&quot;c2&quot;);
+            } finally {
+                append(&quot;f2&quot;);
+            }
+            append(&quot;f1b&quot;);
+        }
+    }
+    append(&quot;x&quot;);
+
+}, &quot;ci,ni,0,t1a,f1a,t2,f2,f1b,ri,x&quot;, undefined, NothingThrown);
+
+// Handling return in a for-of loop in finally block F1 with try-finally in F1's body.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        for (var element of arr) {
+            append(element);
+            return &quot;r1&quot;;
+        }
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        try {
+            append(&quot;t2&quot;);
+        } catch (e) {
+            append(&quot;c2&quot;);
+        } finally {
+            append(&quot;f2&quot;);
+        }
+        append(&quot;f1b&quot;);
+    }
+    append(&quot;x&quot;);
+
+}, &quot;t1a,ci,ni,0,ri,f1a,t2,f2,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling break in a for-of loop in finally block F1 with try-finally in F1's body.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 3);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        for (var element of arr) {
+            append(element);
+            break;
+        }
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        try {
+            append(&quot;t2&quot;);
+        } catch (e) {
+            append(&quot;c2&quot;);
+        } finally {
+            append(&quot;f2&quot;);
+        }
+        append(&quot;f1b&quot;);
+    }
+    append(&quot;x&quot;);
+
+}, &quot;t1a,ci,ni,0,ri,t1b,f1a,t2,f2,f1b,x&quot;, undefined, NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop in F1's body.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        for (var element of arr) {
+            append(element);
+        }
+        append(&quot;f1b&quot;);
+    }
+
+    append(&quot;x&quot;);
+
+}, &quot;t1a,f1a,ci,ni,0,ni,1,ni,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop nesting a try-finally in F1's body.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        for (var element of arr) {
+            append(element);
+            try {
+                append(&quot;t2&quot;);
+            } catch (e) {
+                append(&quot;c2&quot;);
+            } finally {
+                append(&quot;f2&quot;);
+            }
+        }
+        append(&quot;f1b&quot;);
+    }
+
+    append(&quot;x&quot;);
+
+}, &quot;t1a,f1a,ci,ni,0,t2,f2,ni,1,t2,f2,ni,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling return in finally block F1 with a for-of loop in F1's body + break in for-of loop.
+test(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        for (var element of arr) {
+            append(element);
+            break;
+        }
+        append(&quot;f1b&quot;);
+    }
+
+    append(&quot;x&quot;);
+
+}, &quot;t1a,f1a,ci,ni,0,ri,f1b&quot;, &quot;r1&quot;, 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(() =&gt;  {
+    class TestIterator {
+        constructor() {
+            append(&quot;ci&quot;);
+            this.i = 0;
+        }
+        next() {
+            append(&quot;ni&quot;);
+            let done = (this.i == 2);
+            return { done, value: this.i++ };
+        }
+        return() {
+            append(&quot;ri&quot;);
+            return { }
+        }
+    }
+
+    var arr = [];
+    arr[Symbol.iterator] = function() {
+        return new TestIterator();
+    }
+
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        for (var element of arr) {
+            append(element);
+            try {
+                append(&quot;t2&quot;);
+            } catch (e) {
+                append(&quot;c2&quot;);
+            } finally {
+                append(&quot;f2&quot;);
+            }
+            break;
+        }
+        append(&quot;f1b&quot;);
+    }
+
+    append(&quot;x&quot;);
+
+}, &quot;t1a,f1a,ci,ni,0,t2,f2,ri,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+// Handling return in finally block F1 with try-finally in F1's body.
+test(() =&gt;  {
+    try {
+        append(&quot;t1a&quot;);
+        return &quot;r1&quot;;
+        append(&quot;t1b&quot;);
+    } catch(e) {
+        append(&quot;c1&quot;);
+    } finally {
+        append(&quot;f1a&quot;);
+
+        try {
+            append(&quot;t2&quot;);
+            throw &quot;t2&quot;;
+        } catch (e) {
+            append(&quot;c2&quot;);
+            // t2 caught here, and completion type set back to normal.
+        } finally {
+            append(&quot;f2&quot;);
+        }
+        append(&quot;f1b&quot;);
+    }
+
+}, &quot;t1a,f1a,t2,c2,f2,f1b&quot;, &quot;r1&quot;, NothingThrown);
+
+if (this.window)
+    print(&quot;PASSED&quot;);
</ins></span></pre></div>
<a id="trunkSourceJavaScriptCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/ChangeLog (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/ChangeLog        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/JavaScriptCore/ChangeLog        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -1,3 +1,295 @@
</span><ins>+2016-12-22  Mark Lam  &lt;mark.lam@apple.com&gt;
+
+        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 &quot;CompletionType::Throw&quot; 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 &quot;r1&quot;; // 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 &quot;r1&quot;; // 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 &quot;r1&quot;.
+                   }
+
+                   ...
+               }
+
+        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):
+
</ins><span class="cx"> 2016-12-22  Saam Barati  &lt;sbarati@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         WebAssembly: Make the spec-tests/address.wast.js test pass
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecodeHandlerInfoh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecode/HandlerInfo.h (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecode/HandlerInfo.h        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/JavaScriptCore/bytecode/HandlerInfo.h        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -31,9 +31,9 @@
</span><span class="cx"> namespace JSC {
</span><span class="cx"> 
</span><span class="cx"> enum class HandlerType {
</span><del>-    Illegal = 0,
-    Catch = 1,
-    Finally = 2,
</del><ins>+    Catch = 0,
+    Finally = 1,
+    SynthesizedCatch = 2,
</ins><span class="cx">     SynthesizedFinally = 3
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="lines">@@ -53,6 +53,8 @@
</span><span class="cx">             return &quot;catch&quot;;
</span><span class="cx">         case HandlerType::Finally:
</span><span class="cx">             return &quot;finally&quot;;
</span><ins>+        case HandlerType::SynthesizedCatch:
+            return &quot;synthesized catch&quot;;
</ins><span class="cx">         case HandlerType::SynthesizedFinally:
</span><span class="cx">             return &quot;synthesized finally&quot;;
</span><span class="cx">         default:
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecompilerBytecodeGeneratorcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -155,7 +155,6 @@
</span><span class="cx">         if (end &lt;= start)
</span><span class="cx">             continue;
</span><span class="cx">         
</span><del>-        ASSERT(range.tryData-&gt;handlerType != HandlerType::Illegal);
</del><span class="cx">         UnlinkedHandlerInfo info(static_cast&lt;uint32_t&gt;(start), static_cast&lt;uint32_t&gt;(end),
</span><span class="cx">             static_cast&lt;uint32_t&gt;(range.tryData-&gt;target-&gt;bind()), range.tryData-&gt;handlerType);
</span><span class="cx">         m_codeBlock-&gt;addExceptionHandler(info);
</span><span class="lines">@@ -678,21 +677,25 @@
</span><span class="cx">     // because a function's default parameter ExpressionNodes will use temporary registers.
</span><span class="cx">     pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize, TDZRequirement::UnderTDZ);
</span><span class="cx"> 
</span><ins>+    RefPtr&lt;Label&gt; catchLabel = newLabel();
</ins><span class="cx">     TryData* tryFormalParametersData = nullptr;
</span><del>-    if (isAsyncFunctionWrapperParseMode(parseMode) &amp;&amp; !isSimpleParameterList) {
</del><ins>+    bool needTryCatch = isAsyncFunctionWrapperParseMode(parseMode) &amp;&amp; !isSimpleParameterList;
+    if (needTryCatch) {
</ins><span class="cx">         RefPtr&lt;Label&gt; tryFormalParametersStart = emitLabel(newLabel().get());
</span><del>-        tryFormalParametersData = pushTry(tryFormalParametersStart.get());
</del><ins>+        tryFormalParametersData = pushTry(tryFormalParametersStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures, shouldCreateArgumentsVariableInParameterScope);
</span><span class="cx"> 
</span><del>-    if (isAsyncFunctionWrapperParseMode(parseMode) &amp;&amp; !isSimpleParameterList) {
</del><ins>+    if (needTryCatch) {
</ins><span class="cx">         RefPtr&lt;Label&gt; didNotThrow = newLabel();
</span><span class="cx">         emitJump(didNotThrow.get());
</span><del>-        RefPtr&lt;RegisterID&gt; exception = newTemporary();
</del><ins>+        emitLabel(catchLabel.get());
+        popTry(tryFormalParametersData, catchLabel.get());
+
</ins><span class="cx">         RefPtr&lt;RegisterID&gt; thrownValue = newTemporary();
</span><del>-        RefPtr&lt;Label&gt; catchHere = emitLabel(newLabel().get());
-        popTryAndEmitCatch(tryFormalParametersData, exception.get(), thrownValue.get(), catchHere.get(), HandlerType::Catch);
</del><ins>+        RegisterID* unused = newTemporary();
+        emitCatch(unused, thrownValue.get());
</ins><span class="cx"> 
</span><span class="cx">         // return promiseCapability.@reject(thrownValue)
</span><span class="cx">         RefPtr&lt;RegisterID&gt; reject = emitGetById(newTemporary(), promiseCapabilityRegister(), m_vm-&gt;propertyNames-&gt;builtinNames().rejectPrivateName());
</span><span class="lines">@@ -3491,16 +3494,16 @@
</span><span class="cx">     }
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
</del><ins>+RegisterID* BytecodeGenerator::emitReturn(RegisterID* src, ReturnFrom from)
</ins><span class="cx"> {
</span><span class="cx">     if (isConstructor()) {
</span><span class="cx">         bool mightBeDerived = constructorKind() == ConstructorKind::Extends;
</span><span class="cx">         bool srcIsThis = src-&gt;index() == m_thisRegister.index();
</span><span class="cx"> 
</span><del>-        if (mightBeDerived &amp;&amp; srcIsThis)
</del><ins>+        if (mightBeDerived &amp;&amp; (srcIsThis || from == ReturnFrom::Finally))
</ins><span class="cx">             emitTDZCheck(src);
</span><span class="cx"> 
</span><del>-        if (!srcIsThis) {
</del><ins>+        if (!srcIsThis || from == ReturnFrom::Finally) {
</ins><span class="cx">             RefPtr&lt;Label&gt; isObjectLabel = newLabel();
</span><span class="cx">             emitJumpIfTrue(emitIsObject(newTemporary(), src), isObjectLabel.get());
</span><span class="cx"> 
</span><span class="lines">@@ -3678,82 +3681,31 @@
</span><span class="cx">     emitDebugHook(WillLeaveCallFrame, m_scopeNode-&gt;lastLine(), m_scopeNode-&gt;startOffset(), m_scopeNode-&gt;lineStartOffset());
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void BytecodeGenerator::pushFinallyControlFlowScope(StatementNode* finallyBlock)
</del><ins>+FinallyContext* BytecodeGenerator::pushFinallyControlFlowScope(Label* finallyLabel)
</ins><span class="cx"> {
</span><span class="cx">     // Reclaim free label scopes.
</span><span class="cx">     while (m_labelScopes.size() &amp;&amp; !m_labelScopes.last().refCount())
</span><span class="cx">         m_labelScopes.removeLast();
</span><span class="cx"> 
</span><del>-    ControlFlowScope scope;
-    scope.isFinallyBlock = true;
-    FinallyContext context = {
-        finallyBlock,
-        nullptr,
-        nullptr,
-        static_cast&lt;unsigned&gt;(m_controlFlowScopeStack.size()),
-        static_cast&lt;unsigned&gt;(m_switchContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_forInContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_tryContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_labelScopes.size()),
-        static_cast&lt;unsigned&gt;(m_lexicalScopeStack.size()),
-        m_finallyDepth,
-        m_localScopeDepth
-    };
-    scope.finallyContext = context;
-    m_controlFlowScopeStack.append(scope);
-    m_finallyDepth++;
-}
</del><ins>+    ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), FinallyContext(m_currentFinallyContext, finallyLabel, m_finallyDepth));
+    m_controlFlowScopeStack.append(WTFMove(scope));
</ins><span class="cx"> 
</span><del>-void BytecodeGenerator::pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* node)
-{
-    // Reclaim free label scopes.
-    while (m_labelScopes.size() &amp;&amp; !m_labelScopes.last().refCount())
-        m_labelScopes.removeLast();
-
-    ControlFlowScope scope;
-    scope.isFinallyBlock = true;
-    FinallyContext context = {
-        nullptr,
-        iterator,
-        node,
-        static_cast&lt;unsigned&gt;(m_controlFlowScopeStack.size()),
-        static_cast&lt;unsigned&gt;(m_switchContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_forInContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_tryContextStack.size()),
-        static_cast&lt;unsigned&gt;(m_labelScopes.size()),
-        static_cast&lt;unsigned&gt;(m_lexicalScopeStack.size()),
-        m_finallyDepth,
-        m_localScopeDepth
-    };
-    scope.finallyContext = context;
-    m_controlFlowScopeStack.append(scope);
</del><span class="cx">     m_finallyDepth++;
</span><ins>+    m_currentFinallyContext = &amp;m_controlFlowScopeStack.last().finallyContext;
+    return m_currentFinallyContext;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-void BytecodeGenerator::popFinallyControlFlowScope()
</del><ins>+FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
</ins><span class="cx"> {
</span><span class="cx">     ASSERT(m_controlFlowScopeStack.size());
</span><del>-    ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
-    ASSERT(m_controlFlowScopeStack.last().finallyContext.finallyBlock);
-    ASSERT(!m_controlFlowScopeStack.last().finallyContext.iterator);
-    ASSERT(!m_controlFlowScopeStack.last().finallyContext.enumerationNode);
</del><ins>+    ASSERT(m_controlFlowScopeStack.last().isFinallyScope());
</ins><span class="cx">     ASSERT(m_finallyDepth &gt; 0);
</span><del>-    m_controlFlowScopeStack.removeLast();
</del><ins>+    ASSERT(m_currentFinallyContext);
+    m_currentFinallyContext = m_currentFinallyContext-&gt;outerContext();
</ins><span class="cx">     m_finallyDepth--;
</span><ins>+    return m_controlFlowScopeStack.takeLast().finallyContext;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-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 &gt; 0);
-    m_controlFlowScopeStack.removeLast();
-    m_finallyDepth--;
-}
-
</del><span class="cx"> LabelScopePtr BytecodeGenerator::breakTarget(const Identifier&amp; name)
</span><span class="cx"> {
</span><span class="cx">     // Reclaim free label scopes.
</span><span class="lines">@@ -3851,162 +3803,12 @@
</span><span class="cx">     m_topMostScope = addVar();
</span><span class="cx">     emitMove(m_topMostScope, scopeRegister());
</span><span class="cx"> }
</span><del>-    
-void BytecodeGenerator::emitComplexPopScopes(RegisterID* scope, ControlFlowScope* topScope, ControlFlowScope* bottomScope)
-{
-    while (topScope &gt; bottomScope) {
-        // First we count the number of dynamic scopes we need to remove to get
-        // to a finally block.
-        int numberOfNormalScopes = 0;
-        while (topScope &gt; bottomScope) {
-            if (topScope-&gt;isFinallyBlock)
-                break;
-            ++numberOfNormalScopes;
-            --topScope;
-        }
</del><span class="cx"> 
</span><del>-        if (numberOfNormalScopes) {
-            // We need to remove a number of dynamic scopes to get to the next
-            // finally block
-            RefPtr&lt;RegisterID&gt; 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&lt;ControlFlowScope&gt; savedControlFlowScopeStack;
-        Vector&lt;SwitchInfo&gt; savedSwitchContextStack;
-        Vector&lt;RefPtr&lt;ForInContext&gt;&gt; savedForInContextStack;
-        Vector&lt;TryContext&gt; poppedTryContexts;
-        Vector&lt;LexicalScopeStackEntry&gt; savedLexicalScopeStack;
-        LabelScopeStore savedLabelScopes;
-        while (topScope &gt; bottomScope &amp;&amp; topScope-&gt;isFinallyBlock) {
-            RefPtr&lt;Label&gt; 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-&gt;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() &gt; 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() &gt; 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&lt;Label&gt; afterFinally = emitLabel(newLabel().get());
-            
-            // Restore the state of the world.
-            if (flipScopes) {
-                m_controlFlowScopeStack = savedControlFlowScopeStack;
-                topScope = &amp;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)
</del><ins>+TryData* BytecodeGenerator::pushTry(Label* start, Label* handlerLabel, HandlerType handlerType)
</ins><span class="cx"> {
</span><del>-    ASSERT(labelScopeDepth() - targetScopeDepth &gt;= 0);
-
-    size_t scopeDelta = labelScopeDepth() - targetScopeDepth;
-    ASSERT(scopeDelta &lt;= m_controlFlowScopeStack.size());
-    if (!scopeDelta)
-        return;
-
-    if (!m_finallyDepth) {
-        RefPtr&lt;RegisterID&gt; parentScope = newTemporary();
-        while (scopeDelta--) {
-            parentScope = emitGetParentScope(parentScope.get(), scope);
-            emitMove(scope, parentScope.get());
-        }
-        return;
-    }
-
-    emitComplexPopScopes(scope, &amp;m_controlFlowScopeStack.last(), &amp;m_controlFlowScopeStack.last() - scopeDelta);
-}
-
-TryData* BytecodeGenerator::pushTry(Label* start)
-{
</del><span class="cx">     TryData tryData;
</span><del>-    tryData.target = newLabel();
-    tryData.handlerType = HandlerType::Illegal;
</del><ins>+    tryData.target = handlerLabel;
+    tryData.handlerType = handlerType;
</ins><span class="cx">     m_tryData.append(tryData);
</span><span class="cx">     TryData* result = &amp;m_tryData.last();
</span><span class="cx">     
</span><span class="lines">@@ -4019,7 +3821,7 @@
</span><span class="cx">     return result;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-void BytecodeGenerator::popTryAndEmitCatch(TryData* tryData, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType handlerType)
</del><ins>+void BytecodeGenerator::popTry(TryData* tryData, Label* end)
</ins><span class="cx"> {
</span><span class="cx">     m_usesExceptions = true;
</span><span class="cx">     
</span><span class="lines">@@ -4031,28 +3833,52 @@
</span><span class="cx">     tryRange.tryData = m_tryContextStack.last().tryData;
</span><span class="cx">     m_tryRanges.append(tryRange);
</span><span class="cx">     m_tryContextStack.removeLast();
</span><del>-    
-    emitLabel(tryRange.tryData-&gt;target.get());
-    tryRange.tryData-&gt;handlerType = handlerType;
</del><ins>+}
</ins><span class="cx"> 
</span><ins>+void BytecodeGenerator::emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister)
+{
</ins><span class="cx">     emitOpcode(op_catch);
</span><span class="cx">     instructions().append(exceptionRegister-&gt;index());
</span><span class="cx">     instructions().append(thrownValueRegister-&gt;index());
</span><ins>+}
</ins><span class="cx"> 
</span><del>-    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;
</del><ins>+void BytecodeGenerator::restoreScopeRegister(int lexicalScopeIndex)
+{
+    if (lexicalScopeIndex == CurrentLexicalScopeIndex)
+        return; // No change needed.
+
+    if (lexicalScopeIndex != OutermostLexicalScopeIndex) {
+        ASSERT(lexicalScopeIndex &lt; static_cast&lt;int&gt;(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;
+            }
</ins><span class="cx">         }
</span><span class="cx">     }
</span><del>-    if (!foundLocalScope)
-        emitMove(scopeRegister(), m_topMostScope);
</del><ins>+    // 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);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void BytecodeGenerator::restoreScopeRegister()
+{
+    restoreScopeRegister(currentLexicalScopeIndex());
+}
+
+int BytecodeGenerator::labelScopeDepthToLexicalScopeIndex(int targetLabelScopeDepth)
+{
+    ASSERT(labelScopeDepth() - targetLabelScopeDepth &gt;= 0);
+    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+    ASSERT(scopeDelta &lt;= m_controlFlowScopeStack.size());
+    if (!scopeDelta)
+        return CurrentLexicalScopeIndex;
+
+    ControlFlowScope&amp; targetScope = m_controlFlowScopeStack[targetLabelScopeDepth];
+    return targetScope.lexicalScopeIndex;
+}
+
</ins><span class="cx"> int BytecodeGenerator::localScopeDepth() const
</span><span class="cx"> {
</span><span class="cx">     return m_localScopeDepth;
</span><span class="lines">@@ -4059,8 +3885,10 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> int BytecodeGenerator::labelScopeDepth() const
</span><del>-{ 
-    return localScopeDepth() + m_finallyDepth;
</del><ins>+{
+    int depth = localScopeDepth() + m_finallyDepth;
+    ASSERT(depth == static_cast&lt;int&gt;(m_controlFlowScopeStack.size()));
+    return depth;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> void BytecodeGenerator::emitThrowStaticError(ErrorType errorType, RegisterID* raw)
</span><span class="lines">@@ -4130,9 +3958,8 @@
</span><span class="cx"> 
</span><span class="cx"> void BytecodeGenerator::pushLocalControlFlowScope()
</span><span class="cx"> {
</span><del>-    ControlFlowScope scope;
-    scope.isFinallyBlock = false;
-    m_controlFlowScopeStack.append(scope);
</del><ins>+    ControlFlowScope scope(ControlFlowScope::Label, currentLexicalScopeIndex());
+    m_controlFlowScopeStack.append(WTFMove(scope));
</ins><span class="cx">     m_localScopeDepth++;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -4139,7 +3966,7 @@
</span><span class="cx"> void BytecodeGenerator::popLocalControlFlowScope()
</span><span class="cx"> {
</span><span class="cx">     ASSERT(m_controlFlowScopeStack.size());
</span><del>-    ASSERT(!m_controlFlowScopeStack.last().isFinallyBlock);
</del><ins>+    ASSERT(!m_controlFlowScopeStack.last().isFinallyScope());
</ins><span class="cx">     m_controlFlowScopeStack.removeLast();
</span><span class="cx">     m_localScopeDepth--;
</span><span class="cx"> }
</span><span class="lines">@@ -4294,9 +4121,11 @@
</span><span class="cx">     }
</span><span class="cx">     return false;
</span><span class="cx"> }
</span><del>-    
</del><ins>+
</ins><span class="cx"> void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, ExpressionNode* subjectNode, const std::function&lt;void(BytecodeGenerator&amp;, RegisterID*)&gt;&amp; callBack, ForOfNode* forLoopNode, RegisterID* forLoopSymbolTable)
</span><span class="cx"> {
</span><ins>+    CompletionRecordScope completionRecordScope(*this);
+
</ins><span class="cx">     RefPtr&lt;RegisterID&gt; subject = newTemporary();
</span><span class="cx">     emitNode(subject.get(), subjectNode);
</span><span class="cx">     RefPtr&lt;RegisterID&gt; iterator = emitGetById(newTemporary(), subject.get(), propertyNames().iteratorSymbol);
</span><span class="lines">@@ -4307,8 +4136,15 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     RefPtr&lt;Label&gt; loopDone = newLabel();
</span><ins>+    RefPtr&lt;Label&gt; tryStartLabel = newLabel();
+    RefPtr&lt;Label&gt; finallyViaThrowLabel = newLabel();
+    RefPtr&lt;Label&gt; finallyLabel = newLabel();
+    RefPtr&lt;Label&gt; catchLabel = newLabel();
+    RefPtr&lt;Label&gt; endCatchLabel = newLabel();
+
</ins><span class="cx">     // RefPtr&lt;Register&gt; iterator's lifetime must be longer than IteratorCloseContext.
</span><del>-    pushIteratorCloseControlFlowScope(iterator.get(), node);
</del><ins>+    FinallyContext* finallyContext = pushFinallyControlFlowScope(finallyLabel.get());
+
</ins><span class="cx">     {
</span><span class="cx">         LabelScopePtr scope = newLabelScope(LabelScope::Loop);
</span><span class="cx">         RefPtr&lt;RegisterID&gt; value = newTemporary();
</span><span class="lines">@@ -4320,40 +4156,75 @@
</span><span class="cx">         emitLabel(loopStart.get());
</span><span class="cx">         emitLoopHint();
</span><span class="cx"> 
</span><del>-        RefPtr&lt;Label&gt; tryStartLabel = newLabel();
</del><span class="cx">         emitLabel(tryStartLabel.get());
</span><del>-        TryData* tryData = pushTry(tryStartLabel.get());
</del><ins>+        TryData* tryData = pushTry(tryStartLabel.get(), finallyViaThrowLabel.get(), HandlerType::SynthesizedFinally);
</ins><span class="cx">         callBack(*this, value.get());
</span><span class="cx">         emitJump(scope-&gt;continueTarget());
</span><span class="cx"> 
</span><del>-        // IteratorClose sequence for throw-ed control flow.
</del><ins>+        // IteratorClose sequence for abrupt completions.
</ins><span class="cx">         {
</span><del>-            RefPtr&lt;Label&gt; catchHere = emitLabel(newLabel().get());
-            RefPtr&lt;RegisterID&gt; exceptionRegister = newTemporary();
-            RefPtr&lt;RegisterID&gt; thrownValueRegister = newTemporary();
-            popTryAndEmitCatch(tryData, exceptionRegister.get(),
-                thrownValueRegister.get(), catchHere.get(), HandlerType::SynthesizedFinally);
</del><ins>+            // Finally block for the enumeration.
+            emitLabel(finallyViaThrowLabel.get());
+            popTry(tryData, finallyViaThrowLabel.get());
</ins><span class="cx"> 
</span><del>-            RefPtr&lt;Label&gt; catchDone = newLabel();
</del><ins>+            RefPtr&lt;Label&gt; finallyBodyLabel = newLabel();
+            RefPtr&lt;RegisterID&gt; finallyExceptionRegister = newTemporary();
+            RegisterID* unused = newTemporary();
</ins><span class="cx"> 
</span><ins>+            emitCatch(completionValueRegister(), unused);
+            emitSetCompletionType(CompletionType::Throw);
+            emitMove(finallyExceptionRegister.get(), completionValueRegister());
+            emitJump(finallyBodyLabel.get());
+
+            emitLabel(finallyLabel.get());
+            emitMoveEmptyValue(finallyExceptionRegister.get());
+
+            emitLabel(finallyBodyLabel.get());
+            restoreScopeRegister();
+
+            RefPtr&lt;Label&gt; finallyDone = newLabel();
+
</ins><span class="cx">             RefPtr&lt;RegisterID&gt; returnMethod = emitGetById(newTemporary(), iterator.get(), propertyNames().returnKeyword);
</span><del>-            emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), catchDone.get());
</del><ins>+            emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), finallyDone.get());
</ins><span class="cx"> 
</span><span class="cx">             RefPtr&lt;Label&gt; returnCallTryStart = newLabel();
</span><span class="cx">             emitLabel(returnCallTryStart.get());
</span><del>-            TryData* returnCallTryData = pushTry(returnCallTryStart.get());
</del><ins>+            TryData* returnCallTryData = pushTry(returnCallTryStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
</ins><span class="cx"> 
</span><span class="cx">             CallArguments returnArguments(*this, nullptr);
</span><span class="cx">             emitMove(returnArguments.thisRegister(), iterator.get());
</span><span class="cx">             emitCall(value.get(), returnMethod.get(), NoExpectedFunction, returnArguments, node-&gt;divot(), node-&gt;divotStart(), node-&gt;divotEnd(), DebuggableCall::No);
</span><ins>+            emitJumpIfTrue(emitIsObject(newTemporary(), value.get()), finallyDone.get());
+            emitThrowTypeError(ASCIILiteral(&quot;Iterator result interface is not an object.&quot;));
</ins><span class="cx"> 
</span><del>-            emitLabel(catchDone.get());
-            emitThrow(exceptionRegister.get());
</del><ins>+            emitLabel(finallyDone.get());
+            emitFinallyCompletion(*finallyContext, completionTypeRegister(), endCatchLabel.get());
</ins><span class="cx"> 
</span><del>-            // Absorb exception.
-            popTryAndEmitCatch(returnCallTryData, newTemporary(),
-                newTemporary(), catchDone.get(), HandlerType::SynthesizedFinally);
-            emitThrow(exceptionRegister.get());
</del><ins>+            popTry(returnCallTryData, finallyDone.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&lt;RegisterID&gt; 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&lt;Label&gt; throwLabel = newLabel();
+                emitJumpIfTrue(emitIsEmpty(newTemporary(), finallyExceptionRegister.get()), throwLabel.get());
+                emitMove(exceptionRegister.get(), finallyExceptionRegister.get());
+
+                emitLabel(throwLabel.get());
+                emitThrow(exceptionRegister.get());
+
+                emitLabel(endCatchLabel.get());
+            }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         emitLabel(scope-&gt;continueTarget());
</span><span class="lines">@@ -4374,7 +4245,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // IteratorClose sequence for break-ed control flow.
</span><del>-    popIteratorCloseControlFlowScope();
</del><ins>+    popFinallyControlFlowScope();
</ins><span class="cx">     emitIteratorClose(iterator.get(), node);
</span><span class="cx">     emitLabel(loopDone.get());
</span><span class="cx"> }
</span><span class="lines">@@ -4495,6 +4366,14 @@
</span><span class="cx">     return dst;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+RegisterID* BytecodeGenerator::emitIsNumber(RegisterID* dst, RegisterID* src)
+{
+    emitOpcode(op_is_number);
+    instructions().append(dst-&gt;index());
+    instructions().append(src-&gt;index());
+    return dst;
+}
+
</ins><span class="cx"> RegisterID* BytecodeGenerator::emitIsUndefined(RegisterID* dst, RegisterID* src)
</span><span class="cx"> {
</span><span class="cx">     emitOpcode(op_is_undefined);
</span><span class="lines">@@ -4782,11 +4661,9 @@
</span><span class="cx">     // Return.
</span><span class="cx">     {
</span><span class="cx">         RefPtr&lt;RegisterID&gt; returnRegister = generatorValueRegister();
</span><del>-        if (isInFinallyBlock()) {
-            returnRegister = emitMove(newTemporary(), returnRegister.get());
-            emitPopScopes(scopeRegister(), 0);
-        }
-        emitReturn(returnRegister.get());
</del><ins>+        bool hasFinally = emitReturnViaFinallyIfNeeded(returnRegister.get());
+        if (!hasFinally)
+            emitReturn(returnRegister.get());
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Throw.
</span><span class="lines">@@ -4889,9 +4766,9 @@
</span><span class="cx">                     emitGetById(value.get(), value.get(), propertyNames().value);
</span><span class="cx"> 
</span><span class="cx">                     emitLabel(returnSequence.get());
</span><del>-                    if (isInFinallyBlock())
-                        emitPopScopes(scopeRegister(), 0);
-                    emitReturn(value.get());
</del><ins>+                    bool hasFinally = emitReturnViaFinallyIfNeeded(value.get());
+                    if (!hasFinally)
+                        emitReturn(value.get());
</ins><span class="cx">                 }
</span><span class="cx"> 
</span><span class="cx">                 // Normal.
</span><span class="lines">@@ -4921,6 +4798,144 @@
</span><span class="cx">     emitPutById(generatorRegister(), propertyNames().builtinNames().generatorStatePrivateName(), completedState);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget)
+{
+    ASSERT(labelScopeDepth() - targetLabelScopeDepth &gt;= 0);
+    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+    ASSERT(scopeDelta &lt;= m_controlFlowScopeStack.size());
+    if (!scopeDelta)
+        return false; // No finallys to thread through.
+
+    ControlFlowScope* topScope = &amp;m_controlFlowScopeStack.last();
+    ControlFlowScope* bottomScope = &amp;m_controlFlowScopeStack.last() - scopeDelta;
+
+    FinallyContext* innermostFinallyContext = nullptr;
+    FinallyContext* outermostFinallyContext = nullptr;
+    while (topScope &gt; bottomScope) {
+        if (topScope-&gt;isFinallyScope()) {
+            FinallyContext* finallyContext = &amp;topScope-&gt;finallyContext;
+            if (!innermostFinallyContext)
+                innermostFinallyContext = finallyContext;
+            outermostFinallyContext = finallyContext;
+            finallyContext-&gt;incNumberOfBreaksOrContinues();
+        }
+        --topScope;
+    }
+    if (!outermostFinallyContext)
+        return false; // No finallys to thread through.
+
+    auto jumpID = bytecodeOffsetToJumpID(instructions().size());
+    int lexicalScopeIndex = labelScopeDepthToLexicalScopeIndex(targetLabelScopeDepth);
+    outermostFinallyContext-&gt;registerJump(jumpID, lexicalScopeIndex, jumpTarget);
+
+    emitSetCompletionType(jumpID);
+    emitJump(innermostFinallyContext-&gt;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 = &amp;m_controlFlowScopeStack.last();
+    ControlFlowScope* bottomScope = &amp;m_controlFlowScopeStack.first();
+
+    FinallyContext* innermostFinallyContext = nullptr;
+    while (topScope &gt;= bottomScope) {
+        if (topScope-&gt;isFinallyScope()) {
+            FinallyContext* finallyContext = &amp;topScope-&gt;finallyContext;
+            if (!innermostFinallyContext)
+                innermostFinallyContext = finallyContext;
+            finallyContext-&gt;setHandlesReturns();
+        }
+        --topScope;
+    }
+    if (!innermostFinallyContext)
+        return false; // No finallys to thread through.
+
+    emitSetCompletionType(CompletionType::Return);
+    emitSetCompletionValue(returnRegister);
+    emitJump(innermostFinallyContext-&gt;finallyLabel());
+    return true; // We'll be jumping to a finally block.
+}
+
+void BytecodeGenerator::emitFinallyCompletion(FinallyContext&amp; 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 &lt; numberOfJumps; i++) {
+            RefPtr&lt;Label&gt; nextLabel = newLabel();
+            auto&amp; 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() &gt; numberOfJumps;
+            if (hasBreaksOrContinuesNotCoveredByJumps || context.handlesReturns())
+                emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Throw, outerContext-&gt;finallyLabel());
+
+        } else {
+            // We are the outermost finally.
+            if (context.handlesReturns()) {
+                RefPtr&lt;Label&gt; 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 &amp;&amp; m_completionValueRegister);
+    m_completionTypeRegister = nullptr;
+    m_completionValueRegister = nullptr;
+}
+
+void BytecodeGenerator::emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType type, Label* jumpTarget)
+{
+    RefPtr&lt;RegisterID&gt; tempRegister = newTemporary();
+    RegisterID* valueConstant = addConstantValue(jsNumber(static_cast&lt;int&gt;(type)));
+    OperandTypes operandTypes = OperandTypes(ResultType::numberTypeIsInt32(), ResultType::unknownType());
+
+    auto equivalenceResult = emitBinaryOp(compareOpcode, tempRegister.get(), valueConstant, completionTypeRegister, operandTypes);
+    emitJumpIfTrue(equivalenceResult, jumpTarget);
+}
+
</ins><span class="cx"> } // namespace JSC
</span><span class="cx"> 
</span><span class="cx"> namespace WTF {
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecompilerBytecodeGeneratorh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -44,6 +44,7 @@
</span><span class="cx"> #include &quot;TemplateRegistryKey.h&quot;
</span><span class="cx"> #include &quot;UnlinkedCodeBlock.h&quot;
</span><span class="cx"> #include &lt;functional&gt;
</span><ins>+#include &lt;wtf/CheckedArithmetic.h&gt;
</ins><span class="cx"> #include &lt;wtf/HashTraits.h&gt;
</span><span class="cx"> #include &lt;wtf/PassRefPtr.h&gt;
</span><span class="cx"> #include &lt;wtf/SegmentedVector.h&gt;
</span><span class="lines">@@ -80,22 +81,99 @@
</span><span class="cx">         unsigned m_padding;
</span><span class="cx">     };
</span><span class="cx"> 
</span><ins>+    // 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&lt;int&gt;(CompletionType::NumberOfTypes);
+        ASSERT(jumpIDAsInt &gt;= static_cast&lt;int&gt;(CompletionType::NumberOfTypes));
+        return static_cast&lt;CompletionType&gt;(jumpIDAsInt);
+    }
+
+    struct FinallyJump {
+        FinallyJump(CompletionType jumpID, int targetLexicalScopeIndex, Label* targetLabel)
+            : jumpID(jumpID)
+            , targetLexicalScopeIndex(targetLexicalScopeIndex)
+            , targetLabel(targetLabel)
+        { }
+
+        CompletionType jumpID;
+        int targetLexicalScopeIndex;
+        RefPtr&lt;Label&gt; targetLabel;
+    };
+
</ins><span class="cx">     struct FinallyContext {
</span><del>-        StatementNode* finallyBlock;
-        RegisterID* iterator;
-        ThrowableExpressionData* enumerationNode;
-        unsigned controlFlowScopeStackSize;
-        unsigned switchContextStackSize;
-        unsigned forInContextStackSize;
-        unsigned tryContextStackSize;
-        unsigned labelScopesSize;
-        unsigned lexicalScopeStackSize;
-        int finallyDepth;
-        int dynamicScopeDepth;
</del><ins>+        FinallyContext() { }
+        FinallyContext(FinallyContext* outerContext, Label* finallyLabel, int finallyDepth)
+            : m_outerContext(outerContext)
+            , m_finallyLabel(finallyLabel)
+            , m_finallyDepth(finallyDepth)
+        {
+            ASSERT(m_finallyDepth &gt;= 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&amp; jumps(size_t i) { return m_jumps[i]; }
+
+    private:
+        FinallyContext* m_outerContext { nullptr };
+        Label* m_finallyLabel { nullptr };
+        int m_finallyDepth { 0 };
+        Checked&lt;uint32_t, WTF::CrashOnOverflow&gt; m_numberOfBreaksOrContinues;
+        bool m_handlesReturns { false };
+        Vector&lt;FinallyJump&gt; m_jumps;
</ins><span class="cx">     };
</span><span class="cx"> 
</span><span class="cx">     struct ControlFlowScope {
</span><del>-        bool isFinallyBlock;
</del><ins>+        typedef uint8_t Type;
+        enum {
+            Label,
+            Finally
+        };
+        ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext&amp;&amp; finallyContext = FinallyContext())
+            : type(type)
+            , lexicalScopeIndex(lexicalScopeIndex)
+            , finallyContext(std::forward&lt;FinallyContext&gt;(finallyContext))
+        { }
+
+        bool isLabelScope() const { return type == Label; }
+        bool isFinallyScope() const { return type == Finally; }
+
+        Type type;
+        int lexicalScopeIndex;
</ins><span class="cx">         FinallyContext finallyContext;
</span><span class="cx">     };
</span><span class="cx"> 
</span><span class="lines">@@ -605,7 +683,8 @@
</span><span class="cx"> 
</span><span class="cx">         RegisterID* emitGetTemplateObject(RegisterID* dst, TaggedTemplateNode*);
</span><span class="cx"> 
</span><del>-        RegisterID* emitReturn(RegisterID* src);
</del><ins>+        enum class ReturnFrom { Normal, Finally };
+        RegisterID* emitReturn(RegisterID* src, ReturnFrom = ReturnFrom::Normal);
</ins><span class="cx">         RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
</span><span class="cx"> 
</span><span class="cx">         RegisterID* emitConstruct(RegisterID* dst, RegisterID* func, ExpectedFunction, CallArguments&amp;, const JSTextPosition&amp; divot, const JSTextPosition&amp; divotStart, const JSTextPosition&amp; divotEnd);
</span><span class="lines">@@ -626,7 +705,6 @@
</span><span class="cx">         PassRefPtr&lt;Label&gt; emitJumpIfFalse(RegisterID* cond, Label* target);
</span><span class="cx">         PassRefPtr&lt;Label&gt; emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
</span><span class="cx">         PassRefPtr&lt;Label&gt; emitJumpIfNotFunctionApply(RegisterID* cond, Label* target);
</span><del>-        void emitPopScopes(RegisterID* srcDst, int targetScopeDepth);
</del><span class="cx"> 
</span><span class="cx">         void emitEnter();
</span><span class="cx">         void emitWatchdog();
</span><span class="lines">@@ -649,6 +727,7 @@
</span><span class="cx">         RegisterID* emitIsMap(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSMapType); }
</span><span class="cx">         RegisterID* emitIsSet(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSSetType); }
</span><span class="cx">         RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
</span><ins>+        RegisterID* emitIsNumber(RegisterID* dst, RegisterID* src);
</ins><span class="cx">         RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
</span><span class="cx">         RegisterID* emitIsEmpty(RegisterID* dst, RegisterID* src);
</span><span class="cx">         RegisterID* emitIsDerivedArray(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, DerivedArrayType); }
</span><span class="lines">@@ -663,10 +742,31 @@
</span><span class="cx">         bool emitReadOnlyExceptionIfNeeded(const Variable&amp;);
</span><span class="cx"> 
</span><span class="cx">         // Start a try block. 'start' must have been emitted.
</span><del>-        TryData* pushTry(Label* start);
</del><ins>+        TryData* pushTry(Label* start, Label* handlerLabel, HandlerType);
</ins><span class="cx">         // End a try block. 'end' must have been emitted.
</span><del>-        void popTryAndEmitCatch(TryData*, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType);
</del><ins>+        void popTry(TryData*, Label* end);
+        void emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister);
</ins><span class="cx"> 
</span><ins>+    private:
+        static const int CurrentLexicalScopeIndex = -2;
+        static const int OutermostLexicalScopeIndex = -1;
+
+        int currentLexicalScopeIndex() const
+        {
+            int size = static_cast&lt;int&gt;(m_lexicalScopeStack.size());
+            ASSERT(static_cast&lt;size_t&gt;(size) == m_lexicalScopeStack.size());
+            ASSERT(size &gt;= 0);
+            if (!size)
+                return OutermostLexicalScopeIndex;
+            return size - 1;
+        }
+
+    public:
+        void restoreScopeRegister();
+        void restoreScopeRegister(int lexicalScopeIndex);
+
+        int labelScopeDepthToLexicalScopeIndex(int labelScopeDepth);
+
</ins><span class="cx">         void emitThrow(RegisterID* exc)
</span><span class="cx">         { 
</span><span class="cx">             m_usesExceptions = true;
</span><span class="lines">@@ -698,13 +798,59 @@
</span><span class="cx">         void emitDebugHook(ExpressionNode*);
</span><span class="cx">         void emitWillLeaveCallFrameDebugHook();
</span><span class="cx"> 
</span><del>-        bool isInFinallyBlock() { return m_finallyDepth &gt; 0; }
</del><ins>+        class CompletionRecordScope {
+        public:
+            CompletionRecordScope(BytecodeGenerator&amp; generator, bool needCompletionRecordRegisters = true)
+                : m_generator(generator)
+            {
+                if (needCompletionRecordRegisters &amp;&amp; m_generator.allocateCompletionRecordRegisters())
+                    m_needToReleaseOnDestruction = true;
+            }
+            ~CompletionRecordScope()
+            {
+                if (m_needToReleaseOnDestruction)
+                    m_generator.releaseCompletionRecordRegisters();
+            }
</ins><span class="cx"> 
</span><del>-        void pushFinallyControlFlowScope(StatementNode* finallyBlock);
-        void popFinallyControlFlowScope();
-        void pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* enumerationNode);
-        void popIteratorCloseControlFlowScope();
</del><ins>+        private:
+            BytecodeGenerator&amp; m_generator;
+            bool m_needToReleaseOnDestruction { false };
+        };
</ins><span class="cx"> 
</span><ins>+        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&lt;int&gt;(type)));
+        }
+        void emitSetCompletionValue(RegisterID* reg)
+        {
+            emitMove(completionValueRegister(), reg);
+        }
+
+        void emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType, Label* jumpTarget);
+
+        bool emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget);
+        bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
+        void emitFinallyCompletion(FinallyContext&amp;, RegisterID* completionTypeRegister, Label* normalCompletionLabel);
+
+    private:
+        bool allocateCompletionRecordRegisters();
+        void releaseCompletionRecordRegisters();
+
+    public:
+        FinallyContext* pushFinallyControlFlowScope(Label* finallyLabel);
+        FinallyContext popFinallyControlFlowScope();
+
</ins><span class="cx">         void pushIndexedForInScope(RegisterID* local, RegisterID* index);
</span><span class="cx">         void popIndexedForInScope(RegisterID* local);
</span><span class="cx">         void pushStructureForInScope(RegisterID* local, RegisterID* index, RegisterID* property, RegisterID* enumerator);
</span><span class="lines">@@ -797,7 +943,6 @@
</span><span class="cx"> 
</span><span class="cx">         void allocateCalleeSaveSpace();
</span><span class="cx">         void allocateAndEmitScope();
</span><del>-        void emitComplexPopScopes(RegisterID*, ControlFlowScope* topScope, ControlFlowScope* bottomScope);
</del><span class="cx"> 
</span><span class="cx">         typedef HashMap&lt;double, JSValue&gt; NumberMap;
</span><span class="cx">         typedef HashMap&lt;UniquedStringImpl*, JSString*, IdentifierRepHash&gt; IdentifierStringMap;
</span><span class="lines">@@ -935,6 +1080,11 @@
</span><span class="cx">         RegisterID* m_arrowFunctionContextLexicalEnvironmentRegister { nullptr };
</span><span class="cx">         RegisterID* m_promiseCapabilityRegister { nullptr };
</span><span class="cx"> 
</span><ins>+        RefPtr&lt;RegisterID&gt; m_completionTypeRegister;
+        RefPtr&lt;RegisterID&gt; m_completionValueRegister;
+
+        FinallyContext* m_currentFinallyContext { nullptr };
+
</ins><span class="cx">         SegmentedVector&lt;RegisterID*, 16&gt; m_localRegistersForCalleeSaveRegisters;
</span><span class="cx">         SegmentedVector&lt;RegisterID, 32&gt; m_constantPoolRegisters;
</span><span class="cx">         SegmentedVector&lt;RegisterID, 32&gt; m_calleeLocals;
</span><span class="lines">@@ -949,7 +1099,9 @@
</span><span class="cx">         void pushLocalControlFlowScope();
</span><span class="cx">         void popLocalControlFlowScope();
</span><span class="cx"> 
</span><del>-        Vector&lt;ControlFlowScope, 0, UnsafeVectorOverflow&gt; m_controlFlowScopeStack;
</del><ins>+        // FIXME: Restore overflow checking with UnsafeVectorOverflow once SegmentVector supports it.
+        // https://bugs.webkit.org/show_bug.cgi?id=165980
+        SegmentedVector&lt;ControlFlowScope, 16&gt; m_controlFlowScopeStack;
</ins><span class="cx">         Vector&lt;SwitchInfo&gt; m_switchContextStack;
</span><span class="cx">         Vector&lt;RefPtr&lt;ForInContext&gt;&gt; m_forInContextStack;
</span><span class="cx">         Vector&lt;TryContext&gt; m_tryContextStack;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCorebytecompilerNodesCodegencpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -2998,8 +2998,12 @@
</span><span class="cx">     LabelScopePtr scope = generator.continueTarget(m_ident);
</span><span class="cx">     ASSERT(scope);
</span><span class="cx"> 
</span><del>-    generator.emitPopScopes(generator.scopeRegister(), scope-&gt;scopeDepth());
-    generator.emitJump(scope-&gt;continueTarget());
</del><ins>+    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope-&gt;scopeDepth(), scope-&gt;continueTarget());
+    if (!hasFinally) {
+        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope-&gt;scopeDepth());
+        generator.restoreScopeRegister(lexicalScopeIndex);
+        generator.emitJump(scope-&gt;continueTarget());
+    }
</ins><span class="cx"> 
</span><span class="cx">     generator.emitProfileControlFlow(endOffset());
</span><span class="cx"> }
</span><span class="lines">@@ -3025,8 +3029,12 @@
</span><span class="cx">     LabelScopePtr scope = generator.breakTarget(m_ident);
</span><span class="cx">     ASSERT(scope);
</span><span class="cx"> 
</span><del>-    generator.emitPopScopes(generator.scopeRegister(), scope-&gt;scopeDepth());
-    generator.emitJump(scope-&gt;breakTarget());
</del><ins>+    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope-&gt;scopeDepth(), scope-&gt;breakTarget());
+    if (!hasFinally) {
+        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope-&gt;scopeDepth());
+        generator.restoreScopeRegister(lexicalScopeIndex);
+        generator.emitJump(scope-&gt;breakTarget());
+    }
</ins><span class="cx"> 
</span><span class="cx">     generator.emitProfileControlFlow(endOffset());
</span><span class="cx"> }
</span><span class="lines">@@ -3043,14 +3051,14 @@
</span><span class="cx">     RefPtr&lt;RegisterID&gt; returnRegister = m_value ? generator.emitNodeInTailPosition(dst, m_value) : generator.emitLoad(dst, jsUndefined());
</span><span class="cx"> 
</span><span class="cx">     generator.emitProfileType(returnRegister.get(), ProfileTypeBytecodeFunctionReturnStatement, divotStart(), divotEnd());
</span><del>-    if (generator.isInFinallyBlock()) {
-        returnRegister = generator.emitMove(generator.newTemporary(), returnRegister.get());
-        generator.emitPopScopes(generator.scopeRegister(), 0);
</del><ins>+
+    bool hasFinally = generator.emitReturnViaFinallyIfNeeded(returnRegister.get());
+    if (!hasFinally) {
+        generator.emitWillLeaveCallFrameDebugHook();
+        generator.emitReturn(returnRegister.get());
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    generator.emitWillLeaveCallFrameDebugHook();
-    generator.emitReturn(returnRegister.get());
-    generator.emitProfileControlFlow(endOffset()); 
</del><ins>+    generator.emitProfileControlFlow(endOffset());
</ins><span class="cx">     // Emitting an unreachable return here is needed in case this op_profile_control_flow is the 
</span><span class="cx">     // last opcode in a CodeBlock because a CodeBlock's instructions must end with a terminal opcode.
</span><span class="cx">     if (generator.vm()-&gt;controlFlowProfiler())
</span><span class="lines">@@ -3279,32 +3287,56 @@
</span><span class="cx">     // optimizer knows they may be jumped to from anywhere.
</span><span class="cx"> 
</span><span class="cx">     ASSERT(m_catchBlock || m_finallyBlock);
</span><ins>+    BytecodeGenerator::CompletionRecordScope completionRecordScope(generator, m_finallyBlock);
</ins><span class="cx"> 
</span><ins>+    RefPtr&lt;Label&gt; catchLabel;
+    RefPtr&lt;Label&gt; catchEndLabel;
+    RefPtr&lt;Label&gt; finallyViaThrowLabel;
+    RefPtr&lt;Label&gt; finallyLabel;
+    RefPtr&lt;Label&gt; finallyEndLabel;
+
</ins><span class="cx">     RefPtr&lt;Label&gt; tryStartLabel = generator.newLabel();
</span><span class="cx">     generator.emitLabel(tryStartLabel.get());
</span><del>-    
-    if (m_finallyBlock)
-        generator.pushFinallyControlFlowScope(m_finallyBlock);
-    TryData* tryData = generator.pushTry(tryStartLabel.get());
</del><span class="cx"> 
</span><ins>+    if (m_finallyBlock) {
+        finallyViaThrowLabel = generator.newLabel();
+        finallyLabel = generator.newLabel();
+        finallyEndLabel = generator.newLabel();
+
+        generator.pushFinallyControlFlowScope(finallyLabel.get());
+    }
+    if (m_catchBlock) {
+        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);
+
</ins><span class="cx">     generator.emitNode(dst, m_tryBlock);
</span><span class="cx"> 
</span><del>-    if (m_catchBlock) {
-        RefPtr&lt;Label&gt; catchEndLabel = generator.newLabel();
-        
-        // Normal path: jump over the catch block.
</del><ins>+    if (m_finallyBlock)
+        generator.emitJump(finallyLabel.get());
+    else
</ins><span class="cx">         generator.emitJump(catchEndLabel.get());
</span><span class="cx"> 
</span><ins>+    RefPtr&lt;Label&gt; endTryLabel = generator.emitLabel(generator.newLabel().get());
+    generator.popTry(tryData, endTryLabel.get());
+
+    if (m_catchBlock) {
</ins><span class="cx">         // Uncaught exception path: the catch block.
</span><del>-        RefPtr&lt;Label&gt; here = generator.emitLabel(generator.newLabel().get());
-        RefPtr&lt;RegisterID&gt; exceptionRegister = generator.newTemporary();
</del><ins>+        generator.emitLabel(catchLabel.get());
</ins><span class="cx">         RefPtr&lt;RegisterID&gt; thrownValueRegister = generator.newTemporary();
</span><del>-        generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), here.get(), HandlerType::Catch);
-        
</del><ins>+        RegisterID* unused = generator.newTemporary();
+        generator.emitCatch(unused, thrownValueRegister.get());
+        generator.restoreScopeRegister();
+
+        TryData* tryData = nullptr;
</ins><span class="cx">         if (m_finallyBlock) {
</span><span class="cx">             // If the catch block throws an exception and we have a finally block, then the finally
</span><span class="cx">             // block should &quot;catch&quot; that exception.
</span><del>-            tryData = generator.pushTry(here.get());
</del><ins>+            tryData = generator.pushTry(catchLabel.get(), finallyViaThrowLabel.get(), HandlerType::Finally);
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         generator.emitPushCatchScope(m_lexicalVariables);
</span><span class="lines">@@ -3316,37 +3348,41 @@
</span><span class="cx">             generator.emitNodeInTailPosition(dst, m_catchBlock);
</span><span class="cx">         generator.emitLoad(thrownValueRegister.get(), jsUndefined());
</span><span class="cx">         generator.emitPopCatchScope(m_lexicalVariables);
</span><ins>+
+        if (m_finallyBlock) {
+            generator.emitSetCompletionType(CompletionType::Normal);
+            generator.emitJump(finallyLabel.get());
+            generator.popTry(tryData, finallyViaThrowLabel.get());
+        }
+
</ins><span class="cx">         generator.emitLabel(catchEndLabel.get());
</span><ins>+        generator.emitProfileControlFlow(m_catchBlock-&gt;endOffset() + 1);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     if (m_finallyBlock) {
</span><del>-        RefPtr&lt;Label&gt; preFinallyLabel = generator.emitLabel(generator.newLabel().get());
-        
-        generator.popFinallyControlFlowScope();
</del><ins>+        FinallyContext finallyContext = generator.popFinallyControlFlowScope();
</ins><span class="cx"> 
</span><del>-        RefPtr&lt;Label&gt; finallyEndLabel = generator.newLabel();
</del><ins>+        // 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);
</ins><span class="cx"> 
</span><del>-        int finallyStartOffset = m_catchBlock ? m_catchBlock-&gt;endOffset() + 1 : m_tryBlock-&gt;endOffset() + 1;
</del><ins>+        // Entry to the finally block for CompletionTypes other than Throw.
+        generator.emitLabel(finallyLabel.get());
+        generator.restoreScopeRegister();
</ins><span class="cx"> 
</span><del>-        // Normal path: run the finally code, and jump to the end.
-        generator.emitProfileControlFlow(finallyStartOffset);
-        generator.emitNodeInTailPosition(dst, m_finallyBlock);
-        generator.emitProfileControlFlow(m_finallyBlock-&gt;endOffset() + 1);
-        generator.emitJump(finallyEndLabel.get());
</del><ins>+        RefPtr&lt;RegisterID&gt; savedCompletionTypeRegister = generator.newTemporary();
+        generator.emitMove(savedCompletionTypeRegister.get(), generator.completionTypeRegister());
</ins><span class="cx"> 
</span><del>-        // Uncaught exception path: invoke the finally block, then re-throw the exception.
-        RefPtr&lt;RegisterID&gt; exceptionRegister = generator.newTemporary();
-        RefPtr&lt;RegisterID&gt; thrownValueRegister = generator.newTemporary();
-        generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), preFinallyLabel.get(), HandlerType::Finally);
</del><ins>+        int finallyStartOffset = m_catchBlock ? m_catchBlock-&gt;endOffset() + 1 : m_tryBlock-&gt;endOffset() + 1;
</ins><span class="cx">         generator.emitProfileControlFlow(finallyStartOffset);
</span><span class="cx">         generator.emitNodeInTailPosition(dst, m_finallyBlock);
</span><del>-        generator.emitThrow(exceptionRegister.get());
</del><span class="cx"> 
</span><ins>+        generator.emitFinallyCompletion(finallyContext, savedCompletionTypeRegister.get(), finallyEndLabel.get());
</ins><span class="cx">         generator.emitLabel(finallyEndLabel.get());
</span><span class="cx">         generator.emitProfileControlFlow(m_finallyBlock-&gt;endOffset() + 1);
</span><del>-    } else
-        generator.emitProfileControlFlow(m_catchBlock-&gt;endOffset() + 1);
-
</del><ins>+    }
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> // ------------------------------ ScopeNode -----------------------------
</span></span></pre></div>
<a id="trunkSourceWTFChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/ChangeLog (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/ChangeLog        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/WTF/ChangeLog        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -1,3 +1,17 @@
</span><ins>+2016-12-22  Mark Lam  &lt;mark.lam@apple.com&gt;
+
+        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):
+
</ins><span class="cx"> 2016-12-19  Mark Lam  &lt;mark.lam@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Rolling out r209974 and r209952. They break some websites in mysterious ways. Step 2: Rollout r209952.
</span></span></pre></div>
<a id="trunkSourceWTFwtfSegmentedVectorh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WTF/wtf/SegmentedVector.h (210115 => 210116)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WTF/wtf/SegmentedVector.h        2016-12-22 22:40:39 UTC (rev 210115)
+++ trunk/Source/WTF/wtf/SegmentedVector.h        2016-12-22 22:48:32 UTC (rev 210116)
</span><span class="lines">@@ -127,11 +127,35 @@
</span><span class="cx">             return at(index);
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        T&amp; first()
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(0);
+        }
+        const T&amp; first() const
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(0);
+        }
</ins><span class="cx">         T&amp; last()
</span><span class="cx">         {
</span><ins>+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
</ins><span class="cx">             return at(size() - 1);
</span><span class="cx">         }
</span><ins>+        const T&amp; last() const
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            return at(size() - 1);
+        }
</ins><span class="cx"> 
</span><ins>+        T takeLast()
+        {
+            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+            T result = WTFMove(last());
+            --m_size;
+            return result;
+        }
+
</ins><span class="cx">         template&lt;typename... Args&gt;
</span><span class="cx">         void append(Args&amp;&amp;... args)
</span><span class="cx">         {
</span></span></pre>
</div>
</div>

</body>
</html>