Fix incorrect handling of try-finally completion values.
authormark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2019 05:09:53 +0000 (05:09 +0000)
committermark.lam@apple.com <mark.lam@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 7 Mar 2019 05:09:53 +0000 (05:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195131
<rdar://problem/46222079>

Reviewed by Saam Barati and Yusuke Suzuki.

JSTests:

Added many permutations of new test case to test-finally.js.  test-finally.js has
been run on Chrome and Firefox as a sanity check, and we confirmed that all the
tests passes there as well.

* stress/test-finally.js:

Source/JavaScriptCore:

Consider the following:

    function foo() {                        // line 1
        try {
            return 42;                      // line 3
        } finally {
            for (var j = 0; j < 1; j++) {   // line 5
                try {
                    throw '';               // line 7
                } finally {
                    continue;               // line 9
                }
            }
        }                                   // line 11
    }
    var result = foo();

With the current (before fix) code base, result will be the exception object thrown
at line 7.  The expected result should be 42, returned at line 3.

The bug is that we were previously only using one set of completion type and
value registers for the entire function.  This is inadequate because the outer
try-finally needs to preserve its own completion type and value ({ Return, 42 }
in this case) in order to be able to complete correctly.

One might be deceived into thinking that the above example should complete with
the exception thrown at line 7.  However, according to Section 13.15.8 of the
ECMAScript spec, the 'continue' in the finally at line 9 counts as an abrupt
completion.  As a result, it overrides the throw from line 7.  After the continue,
execution resumes at the top of the loop at line 5, followed by a normal completion
at line 11.

Also according to Section 13.15.8, given that the completion type of the outer
finally is normal, the resultant completion of the outer try-finally should be
the completion of the outer try block i.e. { Return, 42 }.

This patch makes the following changes:

1. Fix handling of finally completion to use a unique set of completion
   type and value registers for each FinallyContext.

2. Move the setting of Throw completion type to the out of line exception handler.
   This makes the mainline code slightly less branchy.

3. Introduce emitOutOfLineCatchHandler(), emitOutOfLineFinallyHandler(), and
   emitOutOfLineExceptionHandler() to make it clearer that these are not emitting
   bytecode inline.  Also, these make it clearer when we're emitting a handler
   for a catch vs a finally.

4. Allocate the FinallyContext on the stack instead of as a member of the
   heap allocated ControlFlowScope.  This simplifies its life-cycle management
   and reduces the amount of needed copying.

5. Update emitFinallyCompletion() to propagate the completion type and value to
   the outer FinallyContext when needed.

6. Fix emitJumpIf() to use the right order of operands.  Previously, we were
   only using it to do op_stricteq and op_nstricteq comparisons.  So, the order
   wasn't important.  We now use it to also do op_beloweq comparisons.  Hence,
   the order needs to be corrected.

7. Remove the unused CompletionType::Break and Continue.  These are encoded with
   the jumpIDs of the jump targets instead.

Relevant specifications:
Section 13.15.8: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-try-statement-runtime-semantics-evaluation
Section 6.3.2.4: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-updateempty

* bytecompiler/BytecodeGenerator.cpp:
(JSC::FinallyContext::FinallyContext):
(JSC::BytecodeGenerator::generate):
(JSC::BytecodeGenerator::BytecodeGenerator):
(JSC::BytecodeGenerator::pushFinallyControlFlowScope):
(JSC::BytecodeGenerator::popFinallyControlFlowScope):
(JSC::BytecodeGenerator::emitOutOfLineCatchHandler):
(JSC::BytecodeGenerator::emitOutOfLineFinallyHandler):
(JSC::BytecodeGenerator::emitOutOfLineExceptionHandler):
(JSC::BytecodeGenerator::emitEnumeration):
(JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
(JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
(JSC::BytecodeGenerator::emitFinallyCompletion):
(JSC::BytecodeGenerator::emitJumpIf):
(JSC::BytecodeGenerator::emitCatch): Deleted.
(JSC::BytecodeGenerator::allocateCompletionRecordRegisters): Deleted.
(JSC::BytecodeGenerator::releaseCompletionRecordRegisters): Deleted.
* bytecompiler/BytecodeGenerator.h:
(JSC::FinallyContext::completionTypeRegister const):
(JSC::FinallyContext::completionValueRegister const):
(JSC::ControlFlowScope::ControlFlowScope):
(JSC::BytecodeGenerator::emitLoad):
(JSC::BytecodeGenerator::CompletionRecordScope::CompletionRecordScope): Deleted.
(JSC::BytecodeGenerator::CompletionRecordScope::~CompletionRecordScope): Deleted.
(JSC::BytecodeGenerator::completionTypeRegister const): Deleted.
(JSC::BytecodeGenerator::completionValueRegister const): Deleted.
(JSC::BytecodeGenerator::emitSetCompletionType): Deleted.
(JSC::BytecodeGenerator::emitSetCompletionValue): Deleted.
* bytecompiler/NodesCodegen.cpp:
(JSC::TryNode::emitBytecode):

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

JSTests/ChangeLog
JSTests/stress/test-finally.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

index 007ecb6..2dea08f 100644 (file)
@@ -1,3 +1,17 @@
+2019-03-06  Mark Lam  <mark.lam@apple.com>
+
+        Fix incorrect handling of try-finally completion values.
+        https://bugs.webkit.org/show_bug.cgi?id=195131
+        <rdar://problem/46222079>
+
+        Reviewed by Saam Barati and Yusuke Suzuki.
+
+        Added many permutations of new test case to test-finally.js.  test-finally.js has
+        been run on Chrome and Firefox as a sanity check, and we confirmed that all the
+        tests passes there as well.
+
+        * stress/test-finally.js:
+
 2019-03-06  Saam Barati  <sbarati@apple.com>
 
         Air::reportUsedRegisters must padInterference
index 07bb332..2a5b7f3 100644 (file)
@@ -725,6 +725,660 @@ test(() => {
 
 }, "a,b,c,y,d,z,f", NothingReturned, 600);
 
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        try {
+            append("d");
+        } finally {
+            append("e");
+        }
+        append("f");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,d,e,f", NothingReturned, 100);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        try {
+            append("d");
+        } finally {
+            append("e");
+        }
+        append("f");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,d,e,f", 100, NothingThrown);
+
+test(() => {
+    append("a");
+    label: try {
+        append("b");
+        for (var j = 0; j < 1; j++) {
+            append("x");
+            break label;
+        }
+    } finally {
+        append("c");
+        try {
+            append("d");
+        } finally {
+            append("e");
+        }
+        append("f");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,x,c,d,e,f,g", 700, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", NothingReturned, 100);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", 100, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", NothingReturned, 100);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                throw 42;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", 100, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", NothingReturned, 100);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                return 42;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", 100, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        throw 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", NothingReturned, 100);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                throw 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", NothingReturned, 200);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                return 200;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e", 200, NothingThrown);
+
+test(() => {
+    append("a");
+    try {
+        append("b");
+        return 100;
+    } finally {
+        append("c");
+        for (var j = 0; j < 1; j++) {
+            append("y");
+            try {
+                append("d");
+                continue;
+            } finally {
+                append("e");
+                continue;
+            }
+            append("f");
+        }
+        append("z");
+    }
+    append("g");
+    return 700;
+
+}, "a,b,c,y,d,e,z", 100, NothingThrown);
+
 // No throw or return in for-of loop.
 test(() => {
     class TestIterator {
index ae6e5c6..f76d109 100644 (file)
@@ -1,3 +1,110 @@
+2019-03-06  Mark Lam  <mark.lam@apple.com>
+
+        Fix incorrect handling of try-finally completion values.
+        https://bugs.webkit.org/show_bug.cgi?id=195131
+        <rdar://problem/46222079>
+
+        Reviewed by Saam Barati and Yusuke Suzuki.
+
+        Consider the following:
+
+            function foo() {                        // line 1
+                try {
+                    return 42;                      // line 3
+                } finally {
+                    for (var j = 0; j < 1; j++) {   // line 5
+                        try {
+                            throw '';               // line 7
+                        } finally {
+                            continue;               // line 9
+                        }
+                    }
+                }                                   // line 11
+            }
+            var result = foo();
+
+        With the current (before fix) code base, result will be the exception object thrown
+        at line 7.  The expected result should be 42, returned at line 3.
+
+        The bug is that we were previously only using one set of completion type and
+        value registers for the entire function.  This is inadequate because the outer
+        try-finally needs to preserve its own completion type and value ({ Return, 42 }
+        in this case) in order to be able to complete correctly.
+
+        One might be deceived into thinking that the above example should complete with
+        the exception thrown at line 7.  However, according to Section 13.15.8 of the
+        ECMAScript spec, the 'continue' in the finally at line 9 counts as an abrupt
+        completion.  As a result, it overrides the throw from line 7.  After the continue,
+        execution resumes at the top of the loop at line 5, followed by a normal completion
+        at line 11.
+
+        Also according to Section 13.15.8, given that the completion type of the outer
+        finally is normal, the resultant completion of the outer try-finally should be
+        the completion of the outer try block i.e. { Return, 42 }.
+
+        This patch makes the following changes:
+        
+        1. Fix handling of finally completion to use a unique set of completion
+           type and value registers for each FinallyContext.
+
+        2. Move the setting of Throw completion type to the out of line exception handler.
+           This makes the mainline code slightly less branchy.
+
+        3. Introduce emitOutOfLineCatchHandler(), emitOutOfLineFinallyHandler(), and
+           emitOutOfLineExceptionHandler() to make it clearer that these are not emitting
+           bytecode inline.  Also, these make it clearer when we're emitting a handler
+           for a catch vs a finally.
+
+        4. Allocate the FinallyContext on the stack instead of as a member of the
+           heap allocated ControlFlowScope.  This simplifies its life-cycle management
+           and reduces the amount of needed copying.
+
+        5. Update emitFinallyCompletion() to propagate the completion type and value to
+           the outer FinallyContext when needed.
+
+        6. Fix emitJumpIf() to use the right order of operands.  Previously, we were
+           only using it to do op_stricteq and op_nstricteq comparisons.  So, the order
+           wasn't important.  We now use it to also do op_beloweq comparisons.  Hence,
+           the order needs to be corrected.
+
+        7. Remove the unused CompletionType::Break and Continue.  These are encoded with
+           the jumpIDs of the jump targets instead.
+
+        Relevant specifications:
+        Section 13.15.8: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-try-statement-runtime-semantics-evaluation
+        Section 6.3.2.4: https://www.ecma-international.org/ecma-262/9.0/index.html#sec-updateempty
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::FinallyContext::FinallyContext):
+        (JSC::BytecodeGenerator::generate):
+        (JSC::BytecodeGenerator::BytecodeGenerator):
+        (JSC::BytecodeGenerator::pushFinallyControlFlowScope):
+        (JSC::BytecodeGenerator::popFinallyControlFlowScope):
+        (JSC::BytecodeGenerator::emitOutOfLineCatchHandler):
+        (JSC::BytecodeGenerator::emitOutOfLineFinallyHandler):
+        (JSC::BytecodeGenerator::emitOutOfLineExceptionHandler):
+        (JSC::BytecodeGenerator::emitEnumeration):
+        (JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
+        (JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
+        (JSC::BytecodeGenerator::emitFinallyCompletion):
+        (JSC::BytecodeGenerator::emitJumpIf):
+        (JSC::BytecodeGenerator::emitCatch): Deleted.
+        (JSC::BytecodeGenerator::allocateCompletionRecordRegisters): Deleted.
+        (JSC::BytecodeGenerator::releaseCompletionRecordRegisters): Deleted.
+        * bytecompiler/BytecodeGenerator.h:
+        (JSC::FinallyContext::completionTypeRegister const):
+        (JSC::FinallyContext::completionValueRegister const):
+        (JSC::ControlFlowScope::ControlFlowScope):
+        (JSC::BytecodeGenerator::emitLoad):
+        (JSC::BytecodeGenerator::CompletionRecordScope::CompletionRecordScope): Deleted.
+        (JSC::BytecodeGenerator::CompletionRecordScope::~CompletionRecordScope): Deleted.
+        (JSC::BytecodeGenerator::completionTypeRegister const): Deleted.
+        (JSC::BytecodeGenerator::completionValueRegister const): Deleted.
+        (JSC::BytecodeGenerator::emitSetCompletionType): Deleted.
+        (JSC::BytecodeGenerator::emitSetCompletionValue): Deleted.
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::TryNode::emitBytecode):
+
 2019-03-06  Saam Barati  <sbarati@apple.com>
 
         JSScript should keep the cache file locked for the duration of its existence and should truncate the cache when it is out of date
index c1acc41..7d9ac80 100644 (file)
@@ -183,6 +183,17 @@ void Variable::dump(PrintStream& out) const
         ", isLexicallyScoped = ", m_isLexicallyScoped, "}");
 }
 
+FinallyContext::FinallyContext(BytecodeGenerator& generator, Label& finallyLabel)
+    : m_outerContext(generator.m_currentFinallyContext)
+    , m_finallyLabel(&finallyLabel)
+{
+    ASSERT(m_jumps.isEmpty());
+    m_completionRecord.typeRegister = generator.newTemporary();
+    m_completionRecord.valueRegister = generator.newTemporary();
+    generator.emitLoad(completionTypeRegister(), CompletionType::Normal);
+    generator.moveEmptyValue(completionValueRegister());
+}
+
 ParserError BytecodeGenerator::generate()
 {
     m_codeBlock->setThisRegister(m_thisRegister.virtualRegister());
@@ -245,14 +256,23 @@ ParserError BytecodeGenerator::generate()
         emitUnreachable();
     }
 
-    for (auto& tuple : m_catchesToEmit) {
+    for (auto& tuple : m_exceptionHandlersToEmit) {
         Ref<Label> realCatchTarget = newLabel();
+        TryData* tryData = std::get<0>(tuple);
+
         OpCatch::emit(this, std::get<1>(tuple), std::get<2>(tuple));
         realCatchTarget->setLocation(*this, m_lastInstruction.offset());
+        if (std::get<3>(tuple).isValid()) {
+            RegisterID completionTypeRegister { std::get<3>(tuple) };
+            CompletionType completionType =
+                tryData->handlerType == HandlerType::Finally || tryData->handlerType == HandlerType::SynthesizedFinally
+                ? CompletionType::Throw
+                : CompletionType::Normal;
+            emitLoad(&completionTypeRegister, completionType);
+        }
         m_codeBlock->addJumpTarget(m_lastInstruction.offset());
 
 
-        TryData* tryData = std::get<0>(tuple);
         emitJump(tryData->target.get());
         tryData->target = WTFMove(realCatchTarget);
     }
@@ -788,8 +808,7 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke
         popTry(tryFormalParametersData, catchLabel.get());
 
         RefPtr<RegisterID> thrownValue = newTemporary();
-        RegisterID* unused = newTemporary();
-        emitCatch(unused, thrownValue.get(), tryFormalParametersData);
+        emitOutOfLineCatchHandler(thrownValue.get(), nullptr, tryFormalParametersData);
 
         // return promiseCapability.@reject(thrownValue)
         RefPtr<RegisterID> reject = emitGetById(newTemporary(), promiseCapabilityRegister(), m_vm->propertyNames->builtinNames().rejectPrivateName());
@@ -3513,17 +3532,16 @@ void BytecodeGenerator::emitWillLeaveCallFrameDebugHook()
     emitDebugHook(WillLeaveCallFrame, m_scopeNode->lastLine(), m_scopeNode->startOffset(), m_scopeNode->lineStartOffset());
 }
 
-FinallyContext* BytecodeGenerator::pushFinallyControlFlowScope(Label& finallyLabel)
+void BytecodeGenerator::pushFinallyControlFlowScope(FinallyContext& finallyContext)
 {
-    ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), FinallyContext(m_currentFinallyContext, finallyLabel));
+    ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), &finallyContext);
     m_controlFlowScopeStack.append(WTFMove(scope));
 
     m_finallyDepth++;
-    m_currentFinallyContext = &m_controlFlowScopeStack.last().finallyContext;
-    return m_currentFinallyContext;
+    m_currentFinallyContext = &finallyContext;
 }
 
-FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
+void BytecodeGenerator::popFinallyControlFlowScope()
 {
     ASSERT(m_controlFlowScopeStack.size());
     ASSERT(m_controlFlowScopeStack.last().isFinallyScope());
@@ -3531,7 +3549,7 @@ FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
     ASSERT(m_currentFinallyContext);
     m_currentFinallyContext = m_currentFinallyContext->outerContext();
     m_finallyDepth--;
-    return m_controlFlowScopeStack.takeLast().finallyContext;
+    m_controlFlowScopeStack.removeLast();
 }
 
 LabelScope* BytecodeGenerator::breakTarget(const Identifier& name)
@@ -3642,9 +3660,23 @@ void BytecodeGenerator::popTry(TryData* tryData, Label& end)
     m_tryContextStack.removeLast();
 }
 
-void BytecodeGenerator::emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister, TryData* data)
+void BytecodeGenerator::emitOutOfLineCatchHandler(RegisterID* thrownValueRegister, RegisterID* completionTypeRegister, TryData* data)
 {
-    m_catchesToEmit.append(CatchEntry { data, exceptionRegister, thrownValueRegister });
+    RegisterID* unused = newTemporary();
+    emitOutOfLineExceptionHandler(unused, thrownValueRegister, completionTypeRegister, data);
+}
+
+void BytecodeGenerator::emitOutOfLineFinallyHandler(RegisterID* exceptionRegister, RegisterID* completionTypeRegister, TryData* data)
+{
+    RegisterID* unused = newTemporary();
+    ASSERT(completionTypeRegister);
+    emitOutOfLineExceptionHandler(exceptionRegister, unused, completionTypeRegister, data);
+}
+
+void BytecodeGenerator::emitOutOfLineExceptionHandler(RegisterID* exceptionRegister, RegisterID* thrownValueRegister, RegisterID* completionTypeRegister, TryData* data)
+{
+    VirtualRegister completionTypeVirtualRegister = completionTypeRegister ? completionTypeRegister : VirtualRegister();
+    m_exceptionHandlersToEmit.append(CatchEntry { data, exceptionRegister, thrownValueRegister, completionTypeVirtualRegister });
 }
 
 void BytecodeGenerator::restoreScopeRegister(int lexicalScopeIndex)
@@ -3959,8 +3991,6 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
     bool isForAwait = forLoopNode ? forLoopNode->isForAwait() : false;
     ASSERT(!isForAwait || (isForAwait && isAsyncFunctionParseMode(parseMode())));
 
-    CompletionRecordScope completionRecordScope(*this);
-
     RefPtr<RegisterID> subject = newTemporary();
     emitNode(subject.get(), subjectNode);
     RefPtr<RegisterID> iterator = isForAwait ? emitGetAsyncIterator(subject.get(), node) : emitGetIterator(subject.get(), node);
@@ -3974,7 +4004,8 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
     Ref<Label> endCatchLabel = newLabel();
 
     // RefPtr<Register> iterator's lifetime must be longer than IteratorCloseContext.
-    FinallyContext* finallyContext = pushFinallyControlFlowScope(finallyLabel.get());
+    FinallyContext finallyContext(*this, finallyLabel.get());
+    pushFinallyControlFlowScope(finallyContext);
 
     {
         Ref<LabelScope> scope = newLabelScope(LabelScope::Loop);
@@ -4000,16 +4031,15 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
 
             Ref<Label> finallyBodyLabel = newLabel();
             RefPtr<RegisterID> finallyExceptionRegister = newTemporary();
-            RegisterID* unused = newTemporary();
 
-            emitCatch(completionValueRegister(), unused, tryData);
-            emitSetCompletionType(CompletionType::Throw);
-            move(finallyExceptionRegister.get(), completionValueRegister());
+            emitOutOfLineFinallyHandler(finallyContext.completionValueRegister(), finallyContext.completionTypeRegister(), tryData);
+            move(finallyExceptionRegister.get(), finallyContext.completionValueRegister());
             emitJump(finallyBodyLabel.get());
 
             emitLabel(finallyLabel.get());
             moveEmptyValue(finallyExceptionRegister.get());
 
+            // Finally fall through case.
             emitLabel(finallyBodyLabel.get());
             restoreScopeRegister();
 
@@ -4033,7 +4063,7 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
             emitThrowTypeError("Iterator result interface is not an object."_s);
 
             emitLabel(finallyDone.get());
-            emitFinallyCompletion(*finallyContext, completionTypeRegister(), endCatchLabel.get());
+            emitFinallyCompletion(finallyContext, endCatchLabel.get());
 
             popTry(returnCallTryData, finallyDone.get());
 
@@ -4044,9 +4074,9 @@ void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, Expressio
             // the finally block. Otherwise, we'll let any new exception pass through.
             {
                 emitLabel(catchLabel.get());
+
                 RefPtr<RegisterID> exceptionRegister = newTemporary();
-                RegisterID* unused = newTemporary();
-                emitCatch(exceptionRegister.get(), unused, returnCallTryData);
+                emitOutOfLineFinallyHandler(exceptionRegister.get(), finallyContext.completionTypeRegister(), returnCallTryData);
                 // 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.
@@ -4685,7 +4715,7 @@ bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, La
     while (numberOfScopesToCheckForFinally--) {
         ControlFlowScope* scope = &m_controlFlowScopeStack[scopeIndex--];
         if (scope->isFinallyScope()) {
-            FinallyContext* finallyContext = &scope->finallyContext;
+            FinallyContext* finallyContext = scope->finallyContext;
             if (!innermostFinallyContext)
                 innermostFinallyContext = finallyContext;
             outermostFinallyContext = finallyContext;
@@ -4699,7 +4729,7 @@ bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, La
     int lexicalScopeIndex = labelScopeDepthToLexicalScopeIndex(targetLabelScopeDepth);
     outermostFinallyContext->registerJump(jumpID, lexicalScopeIndex, jumpTarget);
 
-    emitSetCompletionType(jumpID);
+    emitLoad(innermostFinallyContext->completionTypeRegister(), jumpID);
     emitJump(*innermostFinallyContext->finallyLabel());
     return true; // We'll be jumping to a finally block.
 }
@@ -4715,7 +4745,7 @@ bool BytecodeGenerator::emitReturnViaFinallyIfNeeded(RegisterID* returnRegister)
         size_t scopeIndex = --numberOfScopesToCheckForFinally;
         ControlFlowScope* scope = &m_controlFlowScopeStack[scopeIndex];
         if (scope->isFinallyScope()) {
-            FinallyContext* finallyContext = &scope->finallyContext;
+            FinallyContext* finallyContext = scope->finallyContext;
             if (!innermostFinallyContext)
                 innermostFinallyContext = finallyContext;
             finallyContext->setHandlesReturns();
@@ -4724,76 +4754,95 @@ bool BytecodeGenerator::emitReturnViaFinallyIfNeeded(RegisterID* returnRegister)
     if (!innermostFinallyContext)
         return false; // No finallys to thread through.
 
-    emitSetCompletionType(CompletionType::Return);
-    emitSetCompletionValue(returnRegister);
+    emitLoad(innermostFinallyContext->completionTypeRegister(), CompletionType::Return);
+    move(innermostFinallyContext->completionValueRegister(), returnRegister);
     emitJump(*innermostFinallyContext->finallyLabel());
     return true; // We'll be jumping to a finally block.
 }
 
-void BytecodeGenerator::emitFinallyCompletion(FinallyContext& context, RegisterID* completionTypeRegister, Label& normalCompletionLabel)
+void BytecodeGenerator::emitFinallyCompletion(FinallyContext& context, Label& normalCompletionLabel)
 {
     if (context.numberOfBreaksOrContinues() || context.handlesReturns()) {
-        emitJumpIf<OpStricteq>(completionTypeRegister, CompletionType::Normal, normalCompletionLabel);
+        emitJumpIf<OpStricteq>(context.completionTypeRegister(), CompletionType::Normal, normalCompletionLabel);
 
         FinallyContext* outerContext = context.outerContext();
 
         size_t numberOfJumps = context.numberOfJumps();
         ASSERT(outerContext || numberOfJumps == context.numberOfBreaksOrContinues());
 
+        // Handle Break or Continue completions that jumps into this FinallyContext.
         for (size_t i = 0; i < numberOfJumps; i++) {
             Ref<Label> nextLabel = newLabel();
             auto& jump = context.jumps(i);
-            emitJumpIf<OpNstricteq>(completionTypeRegister, jump.jumpID, nextLabel.get());
+            emitJumpIf<OpNstricteq>(context.completionTypeRegister(), jump.jumpID, nextLabel.get());
 
+            // After a Break or Continue, we resume execution and may eventually complete with
+            // Normal completion (unless abruptly completed again). So, pre-emptively set the
+            // completion type to Normal. We can also set the completion value to undefined,
+            // but it will never be used for normal completion anyway. So, we'll skip setting it.
             restoreScopeRegister(jump.targetLexicalScopeIndex);
-            emitSetCompletionType(CompletionType::Normal);
+            emitLoad(context.completionTypeRegister(), CompletionType::Normal);
             emitJump(jump.targetLabel.get());
 
             emitLabel(nextLabel.get());
         }
 
+        // Handle completions that take us out of this FinallyContext.
         if (outerContext) {
-            // We are not the outermost finally.
+            if (context.handlesReturns()) {
+                Ref<Label> isNotReturnLabel = newLabel();
+                emitJumpIf<OpNstricteq>(context.completionTypeRegister(), CompletionType::Return, isNotReturnLabel.get());
+
+                // For Return completion, we need to pass the completion type and value to
+                // the outer finally so that it can return when it's done (unless interrupted
+                // by another abrupt completion).
+                move(outerContext->completionTypeRegister(), context.completionTypeRegister());
+                move(outerContext->completionValueRegister(), context.completionValueRegister());
+                emitJump(*outerContext->finallyLabel());
+
+                emitLabel(isNotReturnLabel.get());
+            }
+
             bool hasBreaksOrContinuesNotCoveredByJumps = context.numberOfBreaksOrContinues() > numberOfJumps;
-            if (hasBreaksOrContinuesNotCoveredByJumps || context.handlesReturns())
-                emitJumpIf<OpNstricteq>(completionTypeRegister, CompletionType::Throw, *outerContext->finallyLabel());
+            if (hasBreaksOrContinuesNotCoveredByJumps) {
+                Ref<Label> isThrowOrNormalLabel = newLabel();
+                emitJumpIf<OpBeloweq>(context.completionTypeRegister(), CompletionType::Throw, isThrowOrNormalLabel.get());
+                static_assert(CompletionType::Throw < CompletionType::Return && CompletionType::Throw < CompletionType::Return, "jumpIDs are above CompletionType::Return");
+
+                // Not Throw means we have a Break or Continue that should be handled by the outer context.
+                // These are for Break or Continue completions that have not reached their jump targets
+                // yet. The outer context needs to run its finally, and resume the jump outwards (unless
+                // interrupted by another abrupt completion). So, we need to pass the completion type to
+                // the outer finally. Again, we can skip the completion value because it's not used for
+                // Break nor Continue.
+                move(outerContext->completionTypeRegister(), context.completionTypeRegister());
+                emitJump(*outerContext->finallyLabel());
+
+                emitLabel(isThrowOrNormalLabel.get());
+            }
 
         } else {
             // We are the outermost finally.
             if (context.handlesReturns()) {
                 Ref<Label> notReturnLabel = newLabel();
-                emitJumpIf<OpNstricteq>(completionTypeRegister, CompletionType::Return, notReturnLabel.get());
+                emitJumpIf<OpNstricteq>(context.completionTypeRegister(), CompletionType::Return, notReturnLabel.get());
 
                 emitWillLeaveCallFrameDebugHook();
-                emitReturn(completionValueRegister(), ReturnFrom::Finally);
-                
+                emitReturn(context.completionValueRegister(), ReturnFrom::Finally);
+
                 emitLabel(notReturnLabel.get());
             }
         }
     }
-    emitJumpIf<OpNstricteq>(completionTypeRegister, CompletionType::Throw, normalCompletionLabel);
-    emitThrow(completionValueRegister());
-}
-
-bool BytecodeGenerator::allocateCompletionRecordRegisters()
-{
-    if (m_completionTypeRegister)
-        return false;
 
-    ASSERT(!m_completionValueRegister);
-    m_completionTypeRegister = newTemporary();
-    m_completionValueRegister = newTemporary();
+    // Handle Throw or Normal completions.
+    emitJumpIf<OpNstricteq>(context.completionTypeRegister(), CompletionType::Throw, normalCompletionLabel);
 
-    emitSetCompletionType(CompletionType::Normal);
-    moveEmptyValue(m_completionValueRegister.get());
-    return true;
-}
-
-void BytecodeGenerator::releaseCompletionRecordRegisters()
-{
-    ASSERT(m_completionTypeRegister && m_completionValueRegister);
-    m_completionTypeRegister = nullptr;
-    m_completionValueRegister = nullptr;
+    // For Throw, we just re-throw the previously caught exception captured in the completion value.
+    // The exception handler will set the completion type to Throw, and re-capture the completion
+    // value if needed (i.e. if the exception handler is for a finally). Hence, no need to set the
+    // completion type and value here.
+    emitThrow(context.completionValueRegister());
 }
 
 template<typename CompareOp>
@@ -4803,7 +4852,7 @@ void BytecodeGenerator::emitJumpIf(RegisterID* completionTypeRegister, Completio
     RegisterID* valueConstant = addConstantValue(jsNumber(static_cast<int>(type)));
     OperandTypes operandTypes = OperandTypes(ResultType::numberTypeIsInt32(), ResultType::unknownType());
 
-    auto equivalenceResult = emitBinaryOp<CompareOp>(tempRegister.get(), valueConstant, completionTypeRegister, operandTypes);
+    auto equivalenceResult = emitBinaryOp<CompareOp>(tempRegister.get(), completionTypeRegister, valueConstant, operandTypes);
     emitJumpIfTrue(equivalenceResult, jumpTarget);
 }
 
index b0ac06f..d8d61f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
  * Copyright (C) 2008 Cameron Zwarich <cwzwarich@uwaterloo.ca>
  * Copyright (C) 2012 Igalia, S.L.
  *
@@ -98,11 +98,8 @@ namespace JSC {
     // Hence, there won't be any collision between jumpIDs and CompletionType enums.
     enum class CompletionType : int {
         Normal,
-        Break,
-        Continue,
-        Return,
         Throw,
-        
+        Return,
         NumberOfTypes
     };
 
@@ -125,18 +122,17 @@ namespace JSC {
         Ref<Label> targetLabel;
     };
 
-    struct FinallyContext {
+    class FinallyContext {
+    public:
         FinallyContext() { }
-        FinallyContext(FinallyContext* outerContext, Label& finallyLabel)
-            : m_outerContext(outerContext)
-            , m_finallyLabel(&finallyLabel)
-        {
-            ASSERT(m_jumps.isEmpty());
-        }
+        FinallyContext(BytecodeGenerator&, Label& finallyLabel);
 
         FinallyContext* outerContext() const { return m_outerContext; }
         Label* finallyLabel() const { return m_finallyLabel; }
 
+        RegisterID* completionTypeRegister() const { return m_completionRecord.typeRegister.get(); }
+        RegisterID* completionValueRegister() const { return m_completionRecord.valueRegister.get(); }
+
         uint32_t numberOfBreaksOrContinues() const { return m_numberOfBreaksOrContinues.unsafeGet(); }
         void incNumberOfBreaksOrContinues() { m_numberOfBreaksOrContinues++; }
 
@@ -157,6 +153,10 @@ namespace JSC {
         Checked<uint32_t, WTF::CrashOnOverflow> m_numberOfBreaksOrContinues;
         bool m_handlesReturns { false };
         Vector<FinallyJump> m_jumps;
+        struct {
+            RefPtr<RegisterID> typeRegister;
+            RefPtr<RegisterID> valueRegister;
+        } m_completionRecord;
     };
 
     struct ControlFlowScope {
@@ -165,10 +165,10 @@ namespace JSC {
             Label,
             Finally
         };
-        ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext&& finallyContext = FinallyContext())
+        ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext* finallyContext = nullptr)
             : type(type)
             , lexicalScopeIndex(lexicalScopeIndex)
-            , finallyContext(std::forward<FinallyContext>(finallyContext))
+            , finallyContext(finallyContext)
         { }
 
         bool isLabelScope() const { return type == Label; }
@@ -176,7 +176,7 @@ namespace JSC {
 
         Type type;
         int lexicalScopeIndex;
-        FinallyContext finallyContext;
+        FinallyContext* finallyContext;
     };
 
     class ForInContext : public RefCounted<ForInContext> {
@@ -364,6 +364,7 @@ namespace JSC {
         WTF_MAKE_NONCOPYABLE(BytecodeGenerator);
 
         friend class BoundLabel;
+        friend class FinallyContext;
         friend class Label;
         friend class IndexedForInContext;
         friend class StructureForInContext;
@@ -875,7 +876,9 @@ namespace JSC {
         TryData* pushTry(Label& start, Label& handlerLabel, HandlerType);
         // End a try block. 'end' must have been emitted.
         void popTry(TryData*, Label& end);
-        void emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister, TryData*);
+
+        void emitOutOfLineCatchHandler(RegisterID* thrownValueRegister, RegisterID* completionTypeRegister, TryData*);
+        void emitOutOfLineFinallyHandler(RegisterID* exceptionRegister, RegisterID* completionTypeRegister, TryData*);
 
     private:
         static const int CurrentLexicalScopeIndex = -2;
@@ -891,6 +894,8 @@ namespace JSC {
             return size - 1;
         }
 
+        void emitOutOfLineExceptionHandler(RegisterID* exceptionRegister, RegisterID* thrownValueRegister, RegisterID* completionTypeRegister, TryData*);
+
     public:
         void restoreScopeRegister();
         void restoreScopeRegister(int lexicalScopeIndex);
@@ -929,43 +934,9 @@ namespace JSC {
         void emitDebugHook(ExpressionNode*);
         void emitWillLeaveCallFrameDebugHook();
 
-        class CompletionRecordScope {
-        public:
-            CompletionRecordScope(BytecodeGenerator& generator, bool needCompletionRecordRegisters = true)
-                : m_generator(generator)
-            {
-                if (needCompletionRecordRegisters && m_generator.allocateCompletionRecordRegisters())
-                    m_needToReleaseOnDestruction = true;
-            }
-            ~CompletionRecordScope()
-            {
-                if (m_needToReleaseOnDestruction)
-                    m_generator.releaseCompletionRecordRegisters();
-            }
-
-        private:
-            BytecodeGenerator& m_generator;
-            bool m_needToReleaseOnDestruction { false };
-        };
-
-        RegisterID* completionTypeRegister() const
-        {
-            ASSERT(m_completionTypeRegister);
-            return m_completionTypeRegister.get();
-        }
-        RegisterID* completionValueRegister() const
+        void emitLoad(RegisterID* completionTypeRegister, CompletionType type)
         {
-            ASSERT(m_completionValueRegister);
-            return m_completionValueRegister.get();
-        }
-
-        void emitSetCompletionType(CompletionType type)
-        {
-            emitLoad(completionTypeRegister(), JSValue(static_cast<int>(type)));
-        }
-        void emitSetCompletionValue(RegisterID* reg)
-        {
-            move(completionValueRegister(), reg);
+            emitLoad(completionTypeRegister, JSValue(static_cast<int>(type)));
         }
 
         template<typename CompareOp>
@@ -973,15 +944,11 @@ namespace JSC {
 
         bool emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label& jumpTarget);
         bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
-        void emitFinallyCompletion(FinallyContext&, RegisterID* completionTypeRegister, Label& normalCompletionLabel);
-
-    private:
-        bool allocateCompletionRecordRegisters();
-        void releaseCompletionRecordRegisters();
+        void emitFinallyCompletion(FinallyContext&, Label& normalCompletionLabel);
 
     public:
-        FinallyContext* pushFinallyControlFlowScope(Label& finallyLabel);
-        FinallyContext popFinallyControlFlowScope();
+        void pushFinallyControlFlowScope(FinallyContext&);
+        void popFinallyControlFlowScope();
 
         void pushIndexedForInScope(RegisterID* local, RegisterID* index);
         void popIndexedForInScope(RegisterID* local);
@@ -1245,9 +1212,6 @@ namespace JSC {
         RegisterID* m_arrowFunctionContextLexicalEnvironmentRegister { nullptr };
         RegisterID* m_promiseCapabilityRegister { nullptr };
 
-        RefPtr<RegisterID> m_completionTypeRegister;
-        RefPtr<RegisterID> m_completionValueRegister;
-
         FinallyContext* m_currentFinallyContext { nullptr };
 
         SegmentedVector<RegisterID*, 16> m_localRegistersForCalleeSaveRegisters;
@@ -1314,11 +1278,10 @@ namespace JSC {
 
         CompactVariableMap::Handle m_cachedVariablesUnderTDZ;
 
-        using CatchEntry = std::tuple<TryData*, VirtualRegister, VirtualRegister>;
-        Vector<CatchEntry> m_catchesToEmit;
+        using CatchEntry = std::tuple<TryData*, VirtualRegister, VirtualRegister, VirtualRegister>;
+        Vector<CatchEntry> m_exceptionHandlersToEmit;
     };
 
-
 } // namespace JSC
 
 namespace WTF {
index 15a8d3d..df9dfd8 100644 (file)
@@ -1,7 +1,7 @@
 /*
 *  Copyright (C) 1999-2002 Harri Porten (porten@kde.org)
 *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
-*  Copyright (C) 2003-2018 Apple Inc. All rights reserved.
+*  Copyright (C) 2003-2019 Apple Inc. All rights reserved.
 *  Copyright (C) 2007 Cameron Zwarich (cwzwarich@uwaterloo.ca)
 *  Copyright (C) 2007 Maks Orlovich
 *  Copyright (C) 2007 Eric Seidel <eric@webkit.org>
@@ -3582,32 +3582,29 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
         generator.emitLoad(dst, jsUndefined());
 
     ASSERT(m_catchBlock || m_finallyBlock);
-    BytecodeGenerator::CompletionRecordScope completionRecordScope(generator, m_finallyBlock);
 
     RefPtr<Label> catchLabel;
     RefPtr<Label> catchEndLabel;
-    RefPtr<Label> finallyViaThrowLabel;
     RefPtr<Label> finallyLabel;
     RefPtr<Label> finallyEndLabel;
-
-    Ref<Label> tryStartLabel = generator.newLabel();
-    generator.emitLabel(tryStartLabel.get());
+    Optional<FinallyContext> finallyContext;
 
     if (m_finallyBlock) {
-        finallyViaThrowLabel = generator.newLabel();
         finallyLabel = generator.newLabel();
         finallyEndLabel = generator.newLabel();
 
-        generator.pushFinallyControlFlowScope(*finallyLabel);
+        finallyContext.emplace(generator, *finallyLabel);
+        generator.pushFinallyControlFlowScope(finallyContext.value());
     }
     if (m_catchBlock) {
         catchLabel = generator.newLabel();
         catchEndLabel = generator.newLabel();
     }
 
-    Label& tryHandlerLabel = m_catchBlock ? *catchLabel : *finallyViaThrowLabel;
+    Ref<Label> tryLabel = generator.newEmittedLabel();
+    Label& tryHandlerLabel = m_catchBlock ? *catchLabel : *finallyLabel;
     HandlerType tryHandlerType = m_catchBlock ? HandlerType::Catch : HandlerType::Finally;
-    TryData* tryData = generator.pushTry(tryStartLabel.get(), tryHandlerLabel, tryHandlerType);
+    TryData* tryData = generator.pushTry(tryLabel.get(), tryHandlerLabel, tryHandlerType);
     TryData* finallyTryData = nullptr;
     if (!m_catchBlock && m_finallyBlock)
         finallyTryData = tryData;
@@ -3619,21 +3616,21 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
     else
         generator.emitJump(*catchEndLabel);
 
-    Ref<Label> endTryLabel = generator.newEmittedLabel();
-    generator.popTry(tryData, endTryLabel.get());
+    Ref<Label> tryEndLabel = generator.newEmittedLabel();
+    generator.popTry(tryData, tryEndLabel.get());
 
     if (m_catchBlock) {
         // Uncaught exception path: the catch block.
         generator.emitLabel(*catchLabel);
         RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
-        RegisterID* unused = generator.newTemporary();
-        generator.emitCatch(unused, thrownValueRegister.get(), tryData);
+        RegisterID* completionTypeRegister = m_finallyBlock ? finallyContext->completionTypeRegister() : nullptr;
+        generator.emitOutOfLineCatchHandler(thrownValueRegister.get(), completionTypeRegister, tryData);
         generator.restoreScopeRegister();
 
         if (m_finallyBlock) {
             // If the catch block throws an exception and we have a finally block, then the finally
             // block should "catch" that exception.
-            finallyTryData = generator.pushTry(*catchLabel, *finallyViaThrowLabel, HandlerType::Finally);
+            finallyTryData = generator.pushTry(*catchLabel, *finallyLabel, HandlerType::Finally);
         }
 
         if (m_catchPattern) {
@@ -3652,9 +3649,9 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
             generator.emitPopCatchScope(m_lexicalVariables);
 
         if (m_finallyBlock) {
-            generator.emitSetCompletionType(CompletionType::Normal);
+            generator.emitLoad(finallyContext->completionTypeRegister(), CompletionType::Normal);
             generator.emitJump(*finallyLabel);
-            generator.popTry(finallyTryData, *finallyViaThrowLabel);
+            generator.popTry(finallyTryData, *finallyLabel);
         }
 
         generator.emitLabel(*catchEndLabel);
@@ -3662,26 +3659,20 @@ void TryNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
     }
 
     if (m_finallyBlock) {
-        FinallyContext finallyContext = generator.popFinallyControlFlowScope();
+        generator.popFinallyControlFlowScope();
 
-        // Entry to the finally block for CompletionType::Throw.
-        generator.emitLabel(*finallyViaThrowLabel);
-        RegisterID* unused = generator.newTemporary();
-        generator.emitCatch(generator.completionValueRegister(), unused, finallyTryData);
-        generator.emitSetCompletionType(CompletionType::Throw);
+        // Entry to the finally block for CompletionType::Throw to be generated later.
+        generator.emitOutOfLineFinallyHandler(finallyContext->completionValueRegister(), finallyContext->completionTypeRegister(), finallyTryData);
 
         // Entry to the finally block for CompletionTypes other than Throw.
         generator.emitLabel(*finallyLabel);
         generator.restoreScopeRegister();
 
-        RefPtr<RegisterID> savedCompletionTypeRegister = generator.newTemporary();
-        generator.move(savedCompletionTypeRegister.get(), generator.completionTypeRegister());
-
         int finallyStartOffset = m_catchBlock ? m_catchBlock->endOffset() + 1 : m_tryBlock->endOffset() + 1;
         generator.emitProfileControlFlow(finallyStartOffset);
         generator.emitNodeInTailPosition(m_finallyBlock);
 
-        generator.emitFinallyCompletion(finallyContext, savedCompletionTypeRegister.get(), *finallyEndLabel);
+        generator.emitFinallyCompletion(finallyContext.value(), *finallyEndLabel);
         generator.emitLabel(*finallyEndLabel);
         generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
     }