Changeset 210116 in webkit


Ignore:
Timestamp:
Dec 22, 2016 2:48:32 PM (7 years ago)
Author:
mark.lam@apple.com
Message:

De-duplicate finally blocks.
https://bugs.webkit.org/show_bug.cgi?id=160168

Reviewed by Saam Barati.

JSTests:

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

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

Source/JavaScriptCore:

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

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

The implementation details:

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

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

  1. The completionTypeRegister will hold a CompletionType value. This is how we encode the CompletionType value to be set:
  1. For Normal, Return, and Throw completion types:
    • The completionTypeRegister is set to CompletionType::Normal, CompletionType::Return, and CompletionType::Throw respectively.
  1. 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.

  1. Each finally block will have 2 entries:
    1. the catch entry.
    2. 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.

  1. 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:

  1. we'll register a jumpID and the breakTarget with the FinallyContext for the outermost finally block that we're supposed to run through.
  2. we'll also increment the numberOfBreaksOrContinues count in each FinallyContext from the innermost to the one for that outermost finally block.
  3. emit bytecode to set the completionTypeRegister to the jumpID.
  4. 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:

  1. emit bytecode to set the completionTypeRegister to CompletionType::Return.
  2. emit bytecode to move the return value into the completionValueRegister.
  3. 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.
  1. What happens in each finally block? ================================== For details on the finally block's catch entry, see "CompletionType::Throw" in (4) above.

The finally block's normal entry will:

  1. restore the scope of the finally block.
  2. save the completionTypeRegister in a savedCompletionTypeRegister.
  3. proceed to execute the body of the finally block.

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


  1. 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.
  1. 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.


  1. 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.

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

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

try {

return "r1"; Sets completionTypeRegister to CompletionType::Return;

} finally {

completionTypeRegister is CompletionType::Return here.

try {

... do stuff.

} finally {

... do more stuff.

}

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

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

}

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

completionTypeRegister is CompletionType::Normal here.
try {

return "r1"; Sets completionTypeRegister to CompletionType::Return;

} finally {

completionTypeRegister is CompletionType::Return here.

try {

... do stuff. No abrupt completions.

} finally {

completionTypeRegister is CompletionType::Return here (from the outer try-finally).
savedCompletionTypeRegister is set to completionTypeRegister (i.e. CompletionType::Return) here.

... do more stuff. No abrupt completions.

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

}

...

}

  1. 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.

  1. 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):

Location:
trunk
Files:
8 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r210115 r210116  
     12016-12-22  Mark Lam  <mark.lam@apple.com>
     2
     3        De-duplicate finally blocks.
     4        https://bugs.webkit.org/show_bug.cgi?id=160168
     5
     6        Reviewed by Saam Barati.
     7
     8        Re-landing r209952 with a few new tests added in test-finally.js.
     9
     10        * stress/deeply-nested-finallys.js: Copied from JSTests/stress/deeply-nested-finallys.js.
     11        - Tests many levels of finally nesting.  This causes the old code to hang (and
     12          crashes eventually) while trying to generate bytecode for the exponentially
     13          duplicated finally blocks.  The new code completes this test almost instantly.
     14
     15        * stress/test-finally.js: Copied from JSTests/stress/test-finally.js.
     16        - Tests control flow through various permutations of finally blocks.
     17
    1182016-12-22  Saam Barati  <sbarati@apple.com>
    219
  • trunk/JSTests/stress/test-finally.js

    r210009 r210116  
    10171017
    10181018}, "c,n,0,n,1,r", NothingReturned, 100);
     1019
     1020// Handling return in finally block F1 with try-finally in F1's body.
     1021test(() =>  {
     1022    try {
     1023        append("t1a");
     1024        return "r1";
     1025        append("t1b");
     1026    } catch(e) {
     1027        append("c1");
     1028    } finally {
     1029        append("f1a");
     1030
     1031        try {
     1032            append("t2");
     1033        } catch (e) {
     1034            append("c2");
     1035        } finally {
     1036            append("f2");
     1037        }
     1038        append("f1b");
     1039    }
     1040
     1041}, "t1a,f1a,t2,f2,f1b", "r1", NothingThrown);
     1042
     1043// Handling return in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
     1044test(() =>  {
     1045    class TestIterator {
     1046        constructor() {
     1047            append("ci");
     1048            this.i = 0;
     1049        }
     1050        next() {
     1051            append("ni");
     1052            let done = (this.i == 3);
     1053            return { done, value: this.i++ };
     1054        }
     1055        return() {
     1056            append("ri");
     1057            return { }
     1058        }
     1059    }
     1060
     1061    var arr = [];
     1062    arr[Symbol.iterator] = function() {
     1063        return new TestIterator();
     1064    }
     1065
     1066    for (var element of arr) {
     1067        append(element);
     1068        try {
     1069            append("t1a");
     1070            return "r1";
     1071            append("t1b");
     1072        } catch(e) {
     1073            append("c1");
     1074        } finally {
     1075            append("f1a");
     1076
     1077            try {
     1078                append("t2");
     1079            } catch (e) {
     1080                append("c2");
     1081            } finally {
     1082                append("f2");
     1083            }
     1084            append("f1b");
     1085        }
     1086    }
     1087    append("x");
     1088
     1089}, "ci,ni,0,t1a,f1a,t2,f2,f1b,ri", "r1", NothingThrown);
     1090
     1091// Handling break in finally block F1 with try-finally in F1's body, with F1 in a for-of loop.
     1092test(() =>  {
     1093    class TestIterator {
     1094        constructor() {
     1095            append("ci");
     1096            this.i = 0;
     1097        }
     1098        next() {
     1099            append("ni");
     1100            let done = (this.i == 3);
     1101            return { done, value: this.i++ };
     1102        }
     1103        return() {
     1104            append("ri");
     1105            return { }
     1106        }
     1107    }
     1108
     1109    var arr = [];
     1110    arr[Symbol.iterator] = function() {
     1111        return new TestIterator();
     1112    }
     1113
     1114    for (var element of arr) {
     1115        append(element);
     1116        try {
     1117            append("t1a");
     1118            break;
     1119            append("t1b");
     1120        } catch(e) {
     1121            append("c1");
     1122        } finally {
     1123            append("f1a");
     1124
     1125            try {
     1126                append("t2");
     1127            } catch (e) {
     1128                append("c2");
     1129            } finally {
     1130                append("f2");
     1131            }
     1132            append("f1b");
     1133        }
     1134    }
     1135    append("x");
     1136
     1137}, "ci,ni,0,t1a,f1a,t2,f2,f1b,ri,x", undefined, NothingThrown);
     1138
     1139// Handling return in a for-of loop in finally block F1 with try-finally in F1's body.
     1140test(() =>  {
     1141    class TestIterator {
     1142        constructor() {
     1143            append("ci");
     1144            this.i = 0;
     1145        }
     1146        next() {
     1147            append("ni");
     1148            let done = (this.i == 3);
     1149            return { done, value: this.i++ };
     1150        }
     1151        return() {
     1152            append("ri");
     1153            return { }
     1154        }
     1155    }
     1156
     1157    var arr = [];
     1158    arr[Symbol.iterator] = function() {
     1159        return new TestIterator();
     1160    }
     1161
     1162    try {
     1163        append("t1a");
     1164        for (var element of arr) {
     1165            append(element);
     1166            return "r1";
     1167        }
     1168        append("t1b");
     1169    } catch(e) {
     1170        append("c1");
     1171    } finally {
     1172        append("f1a");
     1173
     1174        try {
     1175            append("t2");
     1176        } catch (e) {
     1177            append("c2");
     1178        } finally {
     1179            append("f2");
     1180        }
     1181        append("f1b");
     1182    }
     1183    append("x");
     1184
     1185}, "t1a,ci,ni,0,ri,f1a,t2,f2,f1b", "r1", NothingThrown);
     1186
     1187// Handling break in a for-of loop in finally block F1 with try-finally in F1's body.
     1188test(() =>  {
     1189    class TestIterator {
     1190        constructor() {
     1191            append("ci");
     1192            this.i = 0;
     1193        }
     1194        next() {
     1195            append("ni");
     1196            let done = (this.i == 3);
     1197            return { done, value: this.i++ };
     1198        }
     1199        return() {
     1200            append("ri");
     1201            return { }
     1202        }
     1203    }
     1204
     1205    var arr = [];
     1206    arr[Symbol.iterator] = function() {
     1207        return new TestIterator();
     1208    }
     1209
     1210    try {
     1211        append("t1a");
     1212        for (var element of arr) {
     1213            append(element);
     1214            break;
     1215        }
     1216        append("t1b");
     1217    } catch(e) {
     1218        append("c1");
     1219    } finally {
     1220        append("f1a");
     1221
     1222        try {
     1223            append("t2");
     1224        } catch (e) {
     1225            append("c2");
     1226        } finally {
     1227            append("f2");
     1228        }
     1229        append("f1b");
     1230    }
     1231    append("x");
     1232
     1233}, "t1a,ci,ni,0,ri,t1b,f1a,t2,f2,f1b,x", undefined, NothingThrown);
     1234
     1235// Handling return in finally block F1 with a for-of loop in F1's body.
     1236test(() =>  {
     1237    class TestIterator {
     1238        constructor() {
     1239            append("ci");
     1240            this.i = 0;
     1241        }
     1242        next() {
     1243            append("ni");
     1244            let done = (this.i == 2);
     1245            return { done, value: this.i++ };
     1246        }
     1247        return() {
     1248            append("ri");
     1249            return { }
     1250        }
     1251    }
     1252
     1253    var arr = [];
     1254    arr[Symbol.iterator] = function() {
     1255        return new TestIterator();
     1256    }
     1257
     1258    try {
     1259        append("t1a");
     1260        return "r1";
     1261        append("t1b");
     1262    } catch(e) {
     1263        append("c1");
     1264    } finally {
     1265        append("f1a");
     1266
     1267        for (var element of arr) {
     1268            append(element);
     1269        }
     1270        append("f1b");
     1271    }
     1272
     1273    append("x");
     1274
     1275}, "t1a,f1a,ci,ni,0,ni,1,ni,f1b", "r1", NothingThrown);
     1276
     1277// Handling return in finally block F1 with a for-of loop nesting a try-finally in F1's body.
     1278test(() =>  {
     1279    class TestIterator {
     1280        constructor() {
     1281            append("ci");
     1282            this.i = 0;
     1283        }
     1284        next() {
     1285            append("ni");
     1286            let done = (this.i == 2);
     1287            return { done, value: this.i++ };
     1288        }
     1289        return() {
     1290            append("ri");
     1291            return { }
     1292        }
     1293    }
     1294
     1295    var arr = [];
     1296    arr[Symbol.iterator] = function() {
     1297        return new TestIterator();
     1298    }
     1299
     1300    try {
     1301        append("t1a");
     1302        return "r1";
     1303        append("t1b");
     1304    } catch(e) {
     1305        append("c1");
     1306    } finally {
     1307        append("f1a");
     1308
     1309        for (var element of arr) {
     1310            append(element);
     1311            try {
     1312                append("t2");
     1313            } catch (e) {
     1314                append("c2");
     1315            } finally {
     1316                append("f2");
     1317            }
     1318        }
     1319        append("f1b");
     1320    }
     1321
     1322    append("x");
     1323
     1324}, "t1a,f1a,ci,ni,0,t2,f2,ni,1,t2,f2,ni,f1b", "r1", NothingThrown);
     1325
     1326// Handling return in finally block F1 with a for-of loop in F1's body + break in for-of loop.
     1327test(() =>  {
     1328    class TestIterator {
     1329        constructor() {
     1330            append("ci");
     1331            this.i = 0;
     1332        }
     1333        next() {
     1334            append("ni");
     1335            let done = (this.i == 2);
     1336            return { done, value: this.i++ };
     1337        }
     1338        return() {
     1339            append("ri");
     1340            return { }
     1341        }
     1342    }
     1343
     1344    var arr = [];
     1345    arr[Symbol.iterator] = function() {
     1346        return new TestIterator();
     1347    }
     1348
     1349    try {
     1350        append("t1a");
     1351        return "r1";
     1352        append("t1b");
     1353    } catch(e) {
     1354        append("c1");
     1355    } finally {
     1356        append("f1a");
     1357
     1358        for (var element of arr) {
     1359            append(element);
     1360            break;
     1361        }
     1362        append("f1b");
     1363    }
     1364
     1365    append("x");
     1366
     1367}, "t1a,f1a,ci,ni,0,ri,f1b", "r1", NothingThrown);
     1368
     1369// Handling return in finally block F1 with a for-of loop nesting a try-finally in F1's body + break in for-of loop.
     1370test(() =>  {
     1371    class TestIterator {
     1372        constructor() {
     1373            append("ci");
     1374            this.i = 0;
     1375        }
     1376        next() {
     1377            append("ni");
     1378            let done = (this.i == 2);
     1379            return { done, value: this.i++ };
     1380        }
     1381        return() {
     1382            append("ri");
     1383            return { }
     1384        }
     1385    }
     1386
     1387    var arr = [];
     1388    arr[Symbol.iterator] = function() {
     1389        return new TestIterator();
     1390    }
     1391
     1392    try {
     1393        append("t1a");
     1394        return "r1";
     1395        append("t1b");
     1396    } catch(e) {
     1397        append("c1");
     1398    } finally {
     1399        append("f1a");
     1400
     1401        for (var element of arr) {
     1402            append(element);
     1403            try {
     1404                append("t2");
     1405            } catch (e) {
     1406                append("c2");
     1407            } finally {
     1408                append("f2");
     1409            }
     1410            break;
     1411        }
     1412        append("f1b");
     1413    }
     1414
     1415    append("x");
     1416
     1417}, "t1a,f1a,ci,ni,0,t2,f2,ri,f1b", "r1", NothingThrown);
     1418
     1419// Handling return in finally block F1 with try-finally in F1's body.
     1420test(() =>  {
     1421    try {
     1422        append("t1a");
     1423        return "r1";
     1424        append("t1b");
     1425    } catch(e) {
     1426        append("c1");
     1427    } finally {
     1428        append("f1a");
     1429
     1430        try {
     1431            append("t2");
     1432            throw "t2";
     1433        } catch (e) {
     1434            append("c2");
     1435            // t2 caught here, and completion type set back to normal.
     1436        } finally {
     1437            append("f2");
     1438        }
     1439        append("f1b");
     1440    }
     1441
     1442}, "t1a,f1a,t2,c2,f2,f1b", "r1", NothingThrown);
     1443
     1444if (this.window)
     1445    print("PASSED");
  • trunk/Source/JavaScriptCore/ChangeLog

    r210115 r210116  
     12016-12-22  Mark Lam  <mark.lam@apple.com>
     2
     3        De-duplicate finally blocks.
     4        https://bugs.webkit.org/show_bug.cgi?id=160168
     5
     6        Reviewed by Saam Barati.
     7
     8        JS execution can arrive at a finally block when there are abrupt completions from
     9        its try or catch block.  The abrupt completion types include Break,
     10        Continue, Return, and Throw.  The non-abrupt completion type is called Normal
     11        (i.e. the case of a try block falling through to the finally block).
     12
     13        Previously, we enable each of these paths for abrupt completion (except for Throw)
     14        to run the finally block code by duplicating the finally block code at each of
     15        the sites that trigger those completions.  This patch fixes the implementation so
     16        that each of these abrupt completions will set a completionTypeRegister (plus a
     17        completionValueRegister for CompletionType::Return) and then jump to the
     18        relevant finally blocks, and continue to thread through subsequent outer finally
     19        blocks until execution reaches the outermost finally block that the completion
     20        type dictates.  We no longer duplicate the finally block code.
     21
     22        The implementation details:
     23        1. We allocate a pair of registers (completionTypeRegister and completionValueRegister)
     24           just before entering the outermost try-catch-finally scope.
     25
     26           On allocating the registers, we initialize the completionTypeRegister to
     27           CompletionType::Normal, and set the completionValueRegister to the empty
     28           JSValue.
     29
     30        2. The completionTypeRegister will hold a CompletionType value.  This is how we
     31           encode the CompletionType value to be set:
     32
     33           a. For Normal, Return, and Throw completion types:
     34              - The completionTypeRegister is set to CompletionType::Normal,
     35                CompletionType::Return, and CompletionType::Throw respectively.
     36
     37           b. For Break and Continue completion types:
     38              - The completionTypeRegister is set to a unique jumpID where the jumpID is
     39                computed as:
     40
     41                jumpID = CompletionType::NumberOfTypes + bytecodeOffset
     42
     43                The bytecodeOffset used here is the bytecodeOffset of the break or continue
     44                statement that triggered this completion.
     45
     46        3. Each finally block will have 2 entries:
     47           a. the catch entry.
     48           b. the normal entry.
     49
     50           The catch entry is recorded in the codeBlock's exception handler table,
     51           and can only be jumped to by the VM's exception handling mechanism.
     52
     53           The normal entry is recorded in a FinallyContext (at bytecode generation time
     54           only) and is jumped to when we want enter the finally block due any of the
     55           other CompletionTypes.
     56
     57        4. How each completion type works?
     58
     59           CompletionType::Normal
     60           ======================
     61           We normally encounter this when falling through from a try or catch block to
     62           the finally block. 
     63         
     64           For the try block case, since completionTypeRegister is set to Normal by default,
     65           there's nothing more that needs to be done.
     66
     67           For the catch block case, since we entered the catch block with an exception,
     68           completionTypeRegister may be set to Throw.  We'll need to set it to Normal
     69           before jumping to the finally block's normal entry.
     70
     71           CompletionType::Break
     72           =====================
     73           When we emit bytecode for the BreakNode, we check if we have any FinallyContexts
     74           that we need to service before jumping to the breakTarget.  If we don't, then
     75           emit op_jump to the breakTarget as usual.  Otherwise:
     76
     77           a. we'll register a jumpID and the breakTarget with the FinallyContext for the
     78              outermost finally block that we're supposed to run through.
     79           b. we'll also increment the numberOfBreaksOrContinues count in each FinallyContext
     80              from the innermost to the one for that outermost finally block.
     81           c. emit bytecode to set the completionTypeRegister to the jumpID.
     82           d. emit bytecode to jump to the normal entry of the innermost finally block.
     83
     84           Each finally block will take care of cascading to the next outer finally block
     85           as needed (see (5) below).
     86
     87           CompletionType::Continue
     88           ========================
     89           Since continues and breaks work the same way (i.e. with a jump), we handle this
     90           exactly the same way as CompletionType::Break, except that we use the
     91           continueTarget instead of the breakTarget.
     92
     93           CompletionType::Return
     94           ======================
     95           When we emit bytecode for the ReturnNode, we check if we have any FinallyContexts
     96           at all on the m_controlFlowScopeStack.  If we don't, then emit op_ret as usual.
     97           Otherwise:
     98
     99           a. emit bytecode to set the completionTypeRegister to CompletionType::Return.
     100           b. emit bytecode to move the return value into the completionValueRegister.
     101           c. emit bytecode to jump to the normal entry of the innermost finally block.
     102
     103           Each finally block will take care of cascading to the next outer finally block
     104           as needed (see (5) below).
     105
     106           CompletionType::Throw
     107           ======================
     108           At the catch entry a finally block, we:
     109           1. emit an op_catch that stores the caught Exception object in the
     110              completionValueRegister.
     111           2. emit bytecode to set the completionTypeRegister to CompletionType::Throw.
     112           3. Fall through or jump to the finally block's normal entry.
     113
     114        5. What happens in each finally block?
     115           ==================================
     116           For details on the finally block's catch entry, see "CompletionType::Throw" in
     117           (4) above.
     118
     119           The finally block's normal entry will:
     120           1. restore the scope of the finally block.
     121           2. save the completionTypeRegister in a savedCompletionTypeRegister.
     122           3. proceed to execute the body of the finally block.
     123
     124           At the end of the finally block, we will emit bytecode check the
     125           savedCompletionTypeRegister for each completion type see emitFinallyCompletion())
     126           in the following order:
     127         
     128           a. Check for CompletionType::Normal
     129              ================================
     130              If savedCompletionTypeRegister is CompletionType::Normal, jump to the
     131              designated normalCompletion label.  We only need this check this finally
     132              block also needs to check for Break, Continue, or Return.  If not, the
     133              completion type check for CompletionType::Throw below will make this check
     134              redundant.
     135
     136           b. Check for CompletionType::Break and Continue
     137              ============================================
     138              If the FinallyContext for this block has registered FinallyJumps, we'll
     139              check the jumpIDs against the savedCompletionTypeRegister.  If the jumpID
     140              matches, jump to the corresponding jumpTarget.
     141
     142              If no jumpIDs match but the FinallyContext's numberOfBreaksOrContinues is
     143              greater than the number of registered FinallyJumps, then this means that
     144              we have a Break or Continue that needs to be handled by an outer finally
     145              block.  In that case, jump to the next outer finally block's normal entry.
     146             
     147           c. Check for CompletionType::Return
     148              ================================
     149              If this finally block is not the outermost and the savedCompletionTypeRegister
     150              is set to CompletionType::Return, then jump to the next outer finally
     151              block's normal entry.
     152
     153              Otherwise, if this finally block is the outermost and the savedCompletionTypeRegister
     154              is set to CompletionType::Return, then execute op_ret and return the value
     155              in the completionValueRegister.
     156
     157           d. CompletionType::Throw
     158              =====================
     159              If savedCompletionTypeRegister is CompletionType::Throw, then just re-throw the
     160              Exception object in the completionValueRegister.
     161
     162           Detail 1: that we check the savedCompletionTypeRegister (and not the
     163           completionTypeRegister).  This is because the finally block may itself contain
     164           a try-finally, and this inner try-finally may have trashed the completionTypeRegister.
     165           Here's an example:
     166
     167               try {
     168                   return "r1"; // Sets completionTypeRegister to CompletionType::Return;
     169               } finally {
     170                   // completionTypeRegister is CompletionType::Return here.
     171
     172                   try {
     173                       ... // do stuff.
     174                   } finally {
     175                       ... // do more stuff.
     176                   }
     177
     178                   // completionTypeRegister may be anything here depending on what
     179                   // was executed in the inner try-finally block above.
     180
     181                   // Hence, finally completion here must be based on a saved copy of the
     182                   // completionTypeRegister when we entered this finally block.
     183               }
     184
     185           Detail 2: the finally completion for CompletionType::Throw must always explicitly
     186           check if the savedCompletionTypeRegister is CompletionType::Throw before throwing.
     187           We cannot imply that it is so from the Throw case being last.  Here's why:
     188
     189               // completionTypeRegister is CompletionType::Normal here.
     190               try {
     191                   return "r1"; // Sets completionTypeRegister to CompletionType::Return;
     192               } finally {
     193                   // completionTypeRegister is CompletionType::Return here.
     194
     195                   try {
     196                       ... // do stuff.  No abrupt completions.
     197                   } finally {
     198                       // completionTypeRegister is CompletionType::Return here (from the outer try-finally).
     199                       // savedCompletionTypeRegister is set to completionTypeRegister (i.e. CompletionType::Return) here.
     200
     201                       ... // do more stuff.  No abrupt completions.
     202
     203                       // Unless there's an abrupt completion since entering the outer
     204                       // finally block, the savedCompletionTypeRegister will remain set
     205                       // to CompletionType::Return.  If we don't explicitly check if the
     206                       // savedCompletionTypeRegister is CompletionType::Throw before
     207                       // throwing here, we'll end up erroneously throwing "r1".
     208                   }
     209
     210                   ...
     211               }
     212
     213        6. restoreScopeRegister()
     214       
     215           Since the needed scope objects are always stored in a local, we can restore
     216           the scope register by simply moving from that local instead of going through
     217           op_get_parent_scope.
     218
     219        7. m_controlFlowScopeStack needs to be a SegmentedVector instead of a Vector.
     220           This makes it easier to keep a pointer to the FinallyContext on that stack,
     221           and not have to worry about the vector being realloc'ed due to resizing.
     222
     223        Performance appears to be neutral both on ES6SampleBench (run via cli) and the
     224        JSC benchmarks.
     225
     226        Relevant spec references:
     227        https://tc39.github.io/ecma262/#sec-completion-record-specification-type
     228        https://tc39.github.io/ecma262/#sec-try-statement-runtime-semantics-evaluation
     229
     230        * bytecode/HandlerInfo.h:
     231        (JSC::HandlerInfoBase::typeName):
     232        * bytecompiler/BytecodeGenerator.cpp:
     233        (JSC::BytecodeGenerator::generate):
     234        (JSC::BytecodeGenerator::BytecodeGenerator):
     235        (JSC::BytecodeGenerator::emitReturn):
     236        (JSC::BytecodeGenerator::pushFinallyControlFlowScope):
     237        (JSC::BytecodeGenerator::popFinallyControlFlowScope):
     238        (JSC::BytecodeGenerator::allocateAndEmitScope):
     239        (JSC::BytecodeGenerator::pushTry):
     240        (JSC::BytecodeGenerator::popTry):
     241        (JSC::BytecodeGenerator::emitCatch):
     242        (JSC::BytecodeGenerator::restoreScopeRegister):
     243        (JSC::BytecodeGenerator::labelScopeDepthToLexicalScopeIndex):
     244        (JSC::BytecodeGenerator::labelScopeDepth):
     245        (JSC::BytecodeGenerator::pushLocalControlFlowScope):
     246        (JSC::BytecodeGenerator::popLocalControlFlowScope):
     247        (JSC::BytecodeGenerator::emitEnumeration):
     248        (JSC::BytecodeGenerator::emitIsNumber):
     249        (JSC::BytecodeGenerator::emitYield):
     250        (JSC::BytecodeGenerator::emitDelegateYield):
     251        (JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
     252        (JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
     253        (JSC::BytecodeGenerator::emitFinallyCompletion):
     254        (JSC::BytecodeGenerator::allocateCompletionRecordRegisters):
     255        (JSC::BytecodeGenerator::releaseCompletionRecordRegisters):
     256        (JSC::BytecodeGenerator::emitJumpIf):
     257        (JSC::BytecodeGenerator::pushIteratorCloseControlFlowScope): Deleted.
     258        (JSC::BytecodeGenerator::popIteratorCloseControlFlowScope): Deleted.
     259        (JSC::BytecodeGenerator::emitComplexPopScopes): Deleted.
     260        (JSC::BytecodeGenerator::emitPopScopes): Deleted.
     261        (JSC::BytecodeGenerator::popTryAndEmitCatch): Deleted.
     262        * bytecompiler/BytecodeGenerator.h:
     263        (JSC::bytecodeOffsetToJumpID):
     264        (JSC::FinallyJump::FinallyJump):
     265        (JSC::FinallyContext::FinallyContext):
     266        (JSC::FinallyContext::outerContext):
     267        (JSC::FinallyContext::finallyLabel):
     268        (JSC::FinallyContext::depth):
     269        (JSC::FinallyContext::numberOfBreaksOrContinues):
     270        (JSC::FinallyContext::incNumberOfBreaksOrContinues):
     271        (JSC::FinallyContext::handlesReturns):
     272        (JSC::FinallyContext::setHandlesReturns):
     273        (JSC::FinallyContext::registerJump):
     274        (JSC::FinallyContext::numberOfJumps):
     275        (JSC::FinallyContext::jumps):
     276        (JSC::ControlFlowScope::ControlFlowScope):
     277        (JSC::ControlFlowScope::isLabelScope):
     278        (JSC::ControlFlowScope::isFinallyScope):
     279        (JSC::BytecodeGenerator::currentLexicalScopeIndex):
     280        (JSC::BytecodeGenerator::CompletionRecordScope::CompletionRecordScope):
     281        (JSC::BytecodeGenerator::CompletionRecordScope::~CompletionRecordScope):
     282        (JSC::BytecodeGenerator::completionTypeRegister):
     283        (JSC::BytecodeGenerator::completionValueRegister):
     284        (JSC::BytecodeGenerator::emitSetCompletionType):
     285        (JSC::BytecodeGenerator::emitSetCompletionValue):
     286        (JSC::BytecodeGenerator::isInFinallyBlock): Deleted.
     287        * bytecompiler/NodesCodegen.cpp:
     288        (JSC::ContinueNode::emitBytecode):
     289        (JSC::BreakNode::emitBytecode):
     290        (JSC::ReturnNode::emitBytecode):
     291        (JSC::TryNode::emitBytecode):
     292
    12932016-12-22  Saam Barati  <sbarati@apple.com>
    2294
  • trunk/Source/JavaScriptCore/bytecode/HandlerInfo.h

    r210010 r210116  
    3232
    3333enum class HandlerType {
    34     Illegal = 0,
    35     Catch = 1,
    36     Finally = 2,
     34    Catch = 0,
     35    Finally = 1,
     36    SynthesizedCatch = 2,
    3737    SynthesizedFinally = 3
    3838};
     
    5454        case HandlerType::Finally:
    5555            return "finally";
     56        case HandlerType::SynthesizedCatch:
     57            return "synthesized catch";
    5658        case HandlerType::SynthesizedFinally:
    5759            return "synthesized finally";
  • trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp

    r210023 r210116  
    156156            continue;
    157157       
    158         ASSERT(range.tryData->handlerType != HandlerType::Illegal);
    159158        UnlinkedHandlerInfo info(static_cast<uint32_t>(start), static_cast<uint32_t>(end),
    160159            static_cast<uint32_t>(range.tryData->target->bind()), range.tryData->handlerType);
     
    679678    pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize, TDZRequirement::UnderTDZ);
    680679
     680    RefPtr<Label> catchLabel = newLabel();
    681681    TryData* tryFormalParametersData = nullptr;
    682     if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
     682    bool needTryCatch = isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList;
     683    if (needTryCatch) {
    683684        RefPtr<Label> tryFormalParametersStart = emitLabel(newLabel().get());
    684         tryFormalParametersData = pushTry(tryFormalParametersStart.get());
     685        tryFormalParametersData = pushTry(tryFormalParametersStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
    685686    }
    686687
    687688    initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures, shouldCreateArgumentsVariableInParameterScope);
    688689
    689     if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
     690    if (needTryCatch) {
    690691        RefPtr<Label> didNotThrow = newLabel();
    691692        emitJump(didNotThrow.get());
    692         RefPtr<RegisterID> exception = newTemporary();
     693        emitLabel(catchLabel.get());
     694        popTry(tryFormalParametersData, catchLabel.get());
     695
    693696        RefPtr<RegisterID> thrownValue = newTemporary();
    694         RefPtr<Label> catchHere = emitLabel(newLabel().get());
    695         popTryAndEmitCatch(tryFormalParametersData, exception.get(), thrownValue.get(), catchHere.get(), HandlerType::Catch);
     697        RegisterID* unused = newTemporary();
     698        emitCatch(unused, thrownValue.get());
    696699
    697700        // return promiseCapability.@reject(thrownValue)
     
    34923495}
    34933496
    3494 RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
     3497RegisterID* BytecodeGenerator::emitReturn(RegisterID* src, ReturnFrom from)
    34953498{
    34963499    if (isConstructor()) {
     
    34983501        bool srcIsThis = src->index() == m_thisRegister.index();
    34993502
    3500         if (mightBeDerived && srcIsThis)
     3503        if (mightBeDerived && (srcIsThis || from == ReturnFrom::Finally))
    35013504            emitTDZCheck(src);
    35023505
    3503         if (!srcIsThis) {
     3506        if (!srcIsThis || from == ReturnFrom::Finally) {
    35043507            RefPtr<Label> isObjectLabel = newLabel();
    35053508            emitJumpIfTrue(emitIsObject(newTemporary(), src), isObjectLabel.get());
     
    36793682}
    36803683
    3681 void BytecodeGenerator::pushFinallyControlFlowScope(StatementNode* finallyBlock)
     3684FinallyContext* BytecodeGenerator::pushFinallyControlFlowScope(Label* finallyLabel)
    36823685{
    36833686    // Reclaim free label scopes.
     
    36853688        m_labelScopes.removeLast();
    36863689
    3687     ControlFlowScope scope;
    3688     scope.isFinallyBlock = true;
    3689     FinallyContext context = {
    3690         finallyBlock,
    3691         nullptr,
    3692         nullptr,
    3693         static_cast<unsigned>(m_controlFlowScopeStack.size()),
    3694         static_cast<unsigned>(m_switchContextStack.size()),
    3695         static_cast<unsigned>(m_forInContextStack.size()),
    3696         static_cast<unsigned>(m_tryContextStack.size()),
    3697         static_cast<unsigned>(m_labelScopes.size()),
    3698         static_cast<unsigned>(m_lexicalScopeStack.size()),
    3699         m_finallyDepth,
    3700         m_localScopeDepth
    3701     };
    3702     scope.finallyContext = context;
    3703     m_controlFlowScopeStack.append(scope);
     3690    ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), FinallyContext(m_currentFinallyContext, finallyLabel, m_finallyDepth));
     3691    m_controlFlowScopeStack.append(WTFMove(scope));
     3692
    37043693    m_finallyDepth++;
    3705 }
    3706 
    3707 void BytecodeGenerator::pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* node)
    3708 {
    3709     // Reclaim free label scopes.
    3710     while (m_labelScopes.size() && !m_labelScopes.last().refCount())
    3711         m_labelScopes.removeLast();
    3712 
    3713     ControlFlowScope scope;
    3714     scope.isFinallyBlock = true;
    3715     FinallyContext context = {
    3716         nullptr,
    3717         iterator,
    3718         node,
    3719         static_cast<unsigned>(m_controlFlowScopeStack.size()),
    3720         static_cast<unsigned>(m_switchContextStack.size()),
    3721         static_cast<unsigned>(m_forInContextStack.size()),
    3722         static_cast<unsigned>(m_tryContextStack.size()),
    3723         static_cast<unsigned>(m_labelScopes.size()),
    3724         static_cast<unsigned>(m_lexicalScopeStack.size()),
    3725         m_finallyDepth,
    3726         m_localScopeDepth
    3727     };
    3728     scope.finallyContext = context;
    3729     m_controlFlowScopeStack.append(scope);
    3730     m_finallyDepth++;
    3731 }
    3732 
    3733 void BytecodeGenerator::popFinallyControlFlowScope()
     3694    m_currentFinallyContext = &m_controlFlowScopeStack.last().finallyContext;
     3695    return m_currentFinallyContext;
     3696}
     3697
     3698FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
    37343699{
    37353700    ASSERT(m_controlFlowScopeStack.size());
    3736     ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
    3737     ASSERT(m_controlFlowScopeStack.last().finallyContext.finallyBlock);
    3738     ASSERT(!m_controlFlowScopeStack.last().finallyContext.iterator);
    3739     ASSERT(!m_controlFlowScopeStack.last().finallyContext.enumerationNode);
     3701    ASSERT(m_controlFlowScopeStack.last().isFinallyScope());
    37403702    ASSERT(m_finallyDepth > 0);
    3741     m_controlFlowScopeStack.removeLast();
     3703    ASSERT(m_currentFinallyContext);
     3704    m_currentFinallyContext = m_currentFinallyContext->outerContext();
    37423705    m_finallyDepth--;
    3743 }
    3744 
    3745 void BytecodeGenerator::popIteratorCloseControlFlowScope()
    3746 {
    3747     ASSERT(m_controlFlowScopeStack.size());
    3748     ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
    3749     ASSERT(!m_controlFlowScopeStack.last().finallyContext.finallyBlock);
    3750     ASSERT(m_controlFlowScopeStack.last().finallyContext.iterator);
    3751     ASSERT(m_controlFlowScopeStack.last().finallyContext.enumerationNode);
    3752     ASSERT(m_finallyDepth > 0);
    3753     m_controlFlowScopeStack.removeLast();
    3754     m_finallyDepth--;
     3706    return m_controlFlowScopeStack.takeLast().finallyContext;
    37553707}
    37563708
     
    38523804    emitMove(m_topMostScope, scopeRegister());
    38533805}
    3854    
    3855 void BytecodeGenerator::emitComplexPopScopes(RegisterID* scope, ControlFlowScope* topScope, ControlFlowScope* bottomScope)
    3856 {
    3857     while (topScope > bottomScope) {
    3858         // First we count the number of dynamic scopes we need to remove to get
    3859         // to a finally block.
    3860         int numberOfNormalScopes = 0;
    3861         while (topScope > bottomScope) {
    3862             if (topScope->isFinallyBlock)
    3863                 break;
    3864             ++numberOfNormalScopes;
    3865             --topScope;
    3866         }
    3867 
    3868         if (numberOfNormalScopes) {
    3869             // We need to remove a number of dynamic scopes to get to the next
    3870             // finally block
    3871             RefPtr<RegisterID> parentScope = newTemporary();
    3872             while (numberOfNormalScopes--) {
    3873                 parentScope = emitGetParentScope(parentScope.get(), scope);
    3874                 emitMove(scope, parentScope.get());
    3875             }
    3876 
    3877             // If topScope == bottomScope then there isn't a finally block left to emit.
    3878             if (topScope == bottomScope)
    3879                 return;
    3880         }
    3881        
    3882         Vector<ControlFlowScope> savedControlFlowScopeStack;
    3883         Vector<SwitchInfo> savedSwitchContextStack;
    3884         Vector<RefPtr<ForInContext>> savedForInContextStack;
    3885         Vector<TryContext> poppedTryContexts;
    3886         Vector<LexicalScopeStackEntry> savedLexicalScopeStack;
    3887         LabelScopeStore savedLabelScopes;
    3888         while (topScope > bottomScope && topScope->isFinallyBlock) {
    3889             RefPtr<Label> beforeFinally = emitLabel(newLabel().get());
    3890            
    3891             // Save the current state of the world while instating the state of the world
    3892             // for the finally block.
    3893             FinallyContext finallyContext = topScope->finallyContext;
    3894             bool flipScopes = finallyContext.controlFlowScopeStackSize != m_controlFlowScopeStack.size();
    3895             bool flipSwitches = finallyContext.switchContextStackSize != m_switchContextStack.size();
    3896             bool flipForIns = finallyContext.forInContextStackSize != m_forInContextStack.size();
    3897             bool flipTries = finallyContext.tryContextStackSize != m_tryContextStack.size();
    3898             bool flipLabelScopes = finallyContext.labelScopesSize != m_labelScopes.size();
    3899             bool flipLexicalScopeStack = finallyContext.lexicalScopeStackSize != m_lexicalScopeStack.size();
    3900             int topScopeIndex = -1;
    3901             int bottomScopeIndex = -1;
    3902             if (flipScopes) {
    3903                 topScopeIndex = topScope - m_controlFlowScopeStack.begin();
    3904                 bottomScopeIndex = bottomScope - m_controlFlowScopeStack.begin();
    3905                 savedControlFlowScopeStack = m_controlFlowScopeStack;
    3906                 m_controlFlowScopeStack.shrink(finallyContext.controlFlowScopeStackSize);
    3907             }
    3908             if (flipSwitches) {
    3909                 savedSwitchContextStack = m_switchContextStack;
    3910                 m_switchContextStack.shrink(finallyContext.switchContextStackSize);
    3911             }
    3912             if (flipForIns) {
    3913                 savedForInContextStack = m_forInContextStack;
    3914                 m_forInContextStack.shrink(finallyContext.forInContextStackSize);
    3915             }
    3916             if (flipTries) {
    3917                 while (m_tryContextStack.size() != finallyContext.tryContextStackSize) {
    3918                     ASSERT(m_tryContextStack.size() > finallyContext.tryContextStackSize);
    3919                     TryContext context = m_tryContextStack.takeLast();
    3920                     TryRange range;
    3921                     range.start = context.start;
    3922                     range.end = beforeFinally;
    3923                     range.tryData = context.tryData;
    3924                     m_tryRanges.append(range);
    3925                     poppedTryContexts.append(context);
    3926                 }
    3927             }
    3928             if (flipLabelScopes) {
    3929                 savedLabelScopes = m_labelScopes;
    3930                 while (m_labelScopes.size() > finallyContext.labelScopesSize)
    3931                     m_labelScopes.removeLast();
    3932             }
    3933             if (flipLexicalScopeStack) {
    3934                 savedLexicalScopeStack = m_lexicalScopeStack;
    3935                 m_lexicalScopeStack.shrink(finallyContext.lexicalScopeStackSize);
    3936             }
    3937             int savedFinallyDepth = m_finallyDepth;
    3938             m_finallyDepth = finallyContext.finallyDepth;
    3939             int savedDynamicScopeDepth = m_localScopeDepth;
    3940             m_localScopeDepth = finallyContext.dynamicScopeDepth;
    3941            
    3942             if (finallyContext.finallyBlock) {
    3943                 // Emit the finally block.
    3944                 emitNode(finallyContext.finallyBlock);
    3945             } else {
    3946                 // Emit the IteratorClose block.
    3947                 ASSERT(finallyContext.iterator);
    3948                 emitIteratorClose(finallyContext.iterator, finallyContext.enumerationNode);
    3949             }
    3950 
    3951             RefPtr<Label> afterFinally = emitLabel(newLabel().get());
    3952            
    3953             // Restore the state of the world.
    3954             if (flipScopes) {
    3955                 m_controlFlowScopeStack = savedControlFlowScopeStack;
    3956                 topScope = &m_controlFlowScopeStack[topScopeIndex]; // assert it's within bounds
    3957                 bottomScope = m_controlFlowScopeStack.begin() + bottomScopeIndex; // don't assert, since it the index might be -1.
    3958             }
    3959             if (flipSwitches)
    3960                 m_switchContextStack = savedSwitchContextStack;
    3961             if (flipForIns)
    3962                 m_forInContextStack = savedForInContextStack;
    3963             if (flipTries) {
    3964                 ASSERT(m_tryContextStack.size() == finallyContext.tryContextStackSize);
    3965                 for (unsigned i = poppedTryContexts.size(); i--;) {
    3966                     TryContext context = poppedTryContexts[i];
    3967                     context.start = afterFinally;
    3968                     m_tryContextStack.append(context);
    3969                 }
    3970                 poppedTryContexts.clear();
    3971             }
    3972             if (flipLabelScopes)
    3973                 m_labelScopes = savedLabelScopes;
    3974             if (flipLexicalScopeStack)
    3975                 m_lexicalScopeStack = savedLexicalScopeStack;
    3976             m_finallyDepth = savedFinallyDepth;
    3977             m_localScopeDepth = savedDynamicScopeDepth;
    3978            
    3979             --topScope;
    3980         }
    3981     }
    3982 }
    3983 
    3984 void BytecodeGenerator::emitPopScopes(RegisterID* scope, int targetScopeDepth)
    3985 {
    3986     ASSERT(labelScopeDepth() - targetScopeDepth >= 0);
    3987 
    3988     size_t scopeDelta = labelScopeDepth() - targetScopeDepth;
    3989     ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
    3990     if (!scopeDelta)
    3991         return;
    3992 
    3993     if (!m_finallyDepth) {
    3994         RefPtr<RegisterID> parentScope = newTemporary();
    3995         while (scopeDelta--) {
    3996             parentScope = emitGetParentScope(parentScope.get(), scope);
    3997             emitMove(scope, parentScope.get());
    3998         }
    3999         return;
    4000     }
    4001 
    4002     emitComplexPopScopes(scope, &m_controlFlowScopeStack.last(), &m_controlFlowScopeStack.last() - scopeDelta);
    4003 }
    4004 
    4005 TryData* BytecodeGenerator::pushTry(Label* start)
     3806
     3807TryData* BytecodeGenerator::pushTry(Label* start, Label* handlerLabel, HandlerType handlerType)
    40063808{
    40073809    TryData tryData;
    4008     tryData.target = newLabel();
    4009     tryData.handlerType = HandlerType::Illegal;
     3810    tryData.target = handlerLabel;
     3811    tryData.handlerType = handlerType;
    40103812    m_tryData.append(tryData);
    40113813    TryData* result = &m_tryData.last();
     
    40203822}
    40213823
    4022 void BytecodeGenerator::popTryAndEmitCatch(TryData* tryData, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType handlerType)
     3824void BytecodeGenerator::popTry(TryData* tryData, Label* end)
    40233825{
    40243826    m_usesExceptions = true;
     
    40323834    m_tryRanges.append(tryRange);
    40333835    m_tryContextStack.removeLast();
    4034    
    4035     emitLabel(tryRange.tryData->target.get());
    4036     tryRange.tryData->handlerType = handlerType;
    4037 
     3836}
     3837
     3838void BytecodeGenerator::emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister)
     3839{
    40383840    emitOpcode(op_catch);
    40393841    instructions().append(exceptionRegister->index());
    40403842    instructions().append(thrownValueRegister->index());
    4041 
    4042     bool foundLocalScope = false;
    4043     for (unsigned i = m_lexicalScopeStack.size(); i--; ) {
    4044         // Note that if we don't find a local scope in the current function/program,
    4045         // we must grab the outer-most scope of this bytecode generation.
    4046         if (m_lexicalScopeStack[i].m_scope) {
    4047             foundLocalScope = true;
    4048             emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
    4049             break;
    4050         }
    4051     }
    4052     if (!foundLocalScope)
    4053         emitMove(scopeRegister(), m_topMostScope);
     3843}
     3844
     3845void BytecodeGenerator::restoreScopeRegister(int lexicalScopeIndex)
     3846{
     3847    if (lexicalScopeIndex == CurrentLexicalScopeIndex)
     3848        return; // No change needed.
     3849
     3850    if (lexicalScopeIndex != OutermostLexicalScopeIndex) {
     3851        ASSERT(lexicalScopeIndex < static_cast<int>(m_lexicalScopeStack.size()));
     3852        int endIndex = lexicalScopeIndex + 1;
     3853        for (size_t i = endIndex; i--; ) {
     3854            if (m_lexicalScopeStack[i].m_scope) {
     3855                emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
     3856                return;
     3857            }
     3858        }
     3859    }
     3860    // Note that if we don't find a local scope in the current function/program,
     3861    // we must grab the outer-most scope of this bytecode generation.
     3862    emitMove(scopeRegister(), m_topMostScope);
     3863}
     3864
     3865void BytecodeGenerator::restoreScopeRegister()
     3866{
     3867    restoreScopeRegister(currentLexicalScopeIndex());
     3868}
     3869
     3870int BytecodeGenerator::labelScopeDepthToLexicalScopeIndex(int targetLabelScopeDepth)
     3871{
     3872    ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
     3873    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
     3874    ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
     3875    if (!scopeDelta)
     3876        return CurrentLexicalScopeIndex;
     3877
     3878    ControlFlowScope& targetScope = m_controlFlowScopeStack[targetLabelScopeDepth];
     3879    return targetScope.lexicalScopeIndex;
    40543880}
    40553881
     
    40603886
    40613887int BytecodeGenerator::labelScopeDepth() const
    4062 {
    4063     return localScopeDepth() + m_finallyDepth;
     3888{
     3889    int depth = localScopeDepth() + m_finallyDepth;
     3890    ASSERT(depth == static_cast<int>(m_controlFlowScopeStack.size()));
     3891    return depth;
    40643892}
    40653893
     
    41313959void BytecodeGenerator::pushLocalControlFlowScope()
    41323960{
    4133     ControlFlowScope scope;
    4134     scope.isFinallyBlock = false;
    4135     m_controlFlowScopeStack.append(scope);
     3961    ControlFlowScope scope(ControlFlowScope::Label, currentLexicalScopeIndex());
     3962    m_controlFlowScopeStack.append(WTFMove(scope));
    41363963    m_localScopeDepth++;
    41373964}
     
    41403967{
    41413968    ASSERT(m_controlFlowScopeStack.size());
    4142     ASSERT(!m_controlFlowScopeStack.last().isFinallyBlock);
     3969    ASSERT(!m_controlFlowScopeStack.last().isFinallyScope());
    41433970    m_controlFlowScopeStack.removeLast();
    41443971    m_localScopeDepth--;
     
    42954122    return false;
    42964123}
    4297    
     4124
    42984125void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, ExpressionNode* subjectNode, const std::function<void(BytecodeGenerator&, RegisterID*)>& callBack, ForOfNode* forLoopNode, RegisterID* forLoopSymbolTable)
    42994126{
     4127    CompletionRecordScope completionRecordScope(*this);
     4128
    43004129    RefPtr<RegisterID> subject = newTemporary();
    43014130    emitNode(subject.get(), subjectNode);
     
    43084137
    43094138    RefPtr<Label> loopDone = newLabel();
     4139    RefPtr<Label> tryStartLabel = newLabel();
     4140    RefPtr<Label> finallyViaThrowLabel = newLabel();
     4141    RefPtr<Label> finallyLabel = newLabel();
     4142    RefPtr<Label> catchLabel = newLabel();
     4143    RefPtr<Label> endCatchLabel = newLabel();
     4144
    43104145    // RefPtr<Register> iterator's lifetime must be longer than IteratorCloseContext.
    4311     pushIteratorCloseControlFlowScope(iterator.get(), node);
     4146    FinallyContext* finallyContext = pushFinallyControlFlowScope(finallyLabel.get());
     4147
    43124148    {
    43134149        LabelScopePtr scope = newLabelScope(LabelScope::Loop);
     
    43214157        emitLoopHint();
    43224158
    4323         RefPtr<Label> tryStartLabel = newLabel();
    43244159        emitLabel(tryStartLabel.get());
    4325         TryData* tryData = pushTry(tryStartLabel.get());
     4160        TryData* tryData = pushTry(tryStartLabel.get(), finallyViaThrowLabel.get(), HandlerType::SynthesizedFinally);
    43264161        callBack(*this, value.get());
    43274162        emitJump(scope->continueTarget());
    43284163
    4329         // IteratorClose sequence for throw-ed control flow.
     4164        // IteratorClose sequence for abrupt completions.
    43304165        {
    4331             RefPtr<Label> catchHere = emitLabel(newLabel().get());
    4332             RefPtr<RegisterID> exceptionRegister = newTemporary();
    4333             RefPtr<RegisterID> thrownValueRegister = newTemporary();
    4334             popTryAndEmitCatch(tryData, exceptionRegister.get(),
    4335                 thrownValueRegister.get(), catchHere.get(), HandlerType::SynthesizedFinally);
    4336 
    4337             RefPtr<Label> catchDone = newLabel();
     4166            // Finally block for the enumeration.
     4167            emitLabel(finallyViaThrowLabel.get());
     4168            popTry(tryData, finallyViaThrowLabel.get());
     4169
     4170            RefPtr<Label> finallyBodyLabel = newLabel();
     4171            RefPtr<RegisterID> finallyExceptionRegister = newTemporary();
     4172            RegisterID* unused = newTemporary();
     4173
     4174            emitCatch(completionValueRegister(), unused);
     4175            emitSetCompletionType(CompletionType::Throw);
     4176            emitMove(finallyExceptionRegister.get(), completionValueRegister());
     4177            emitJump(finallyBodyLabel.get());
     4178
     4179            emitLabel(finallyLabel.get());
     4180            emitMoveEmptyValue(finallyExceptionRegister.get());
     4181
     4182            emitLabel(finallyBodyLabel.get());
     4183            restoreScopeRegister();
     4184
     4185            RefPtr<Label> finallyDone = newLabel();
    43384186
    43394187            RefPtr<RegisterID> returnMethod = emitGetById(newTemporary(), iterator.get(), propertyNames().returnKeyword);
    4340             emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), catchDone.get());
     4188            emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), finallyDone.get());
    43414189
    43424190            RefPtr<Label> returnCallTryStart = newLabel();
    43434191            emitLabel(returnCallTryStart.get());
    4344             TryData* returnCallTryData = pushTry(returnCallTryStart.get());
     4192            TryData* returnCallTryData = pushTry(returnCallTryStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
    43454193
    43464194            CallArguments returnArguments(*this, nullptr);
    43474195            emitMove(returnArguments.thisRegister(), iterator.get());
    43484196            emitCall(value.get(), returnMethod.get(), NoExpectedFunction, returnArguments, node->divot(), node->divotStart(), node->divotEnd(), DebuggableCall::No);
    4349 
    4350             emitLabel(catchDone.get());
    4351             emitThrow(exceptionRegister.get());
    4352 
    4353             // Absorb exception.
    4354             popTryAndEmitCatch(returnCallTryData, newTemporary(),
    4355                 newTemporary(), catchDone.get(), HandlerType::SynthesizedFinally);
    4356             emitThrow(exceptionRegister.get());
     4197            emitJumpIfTrue(emitIsObject(newTemporary(), value.get()), finallyDone.get());
     4198            emitThrowTypeError(ASCIILiteral("Iterator result interface is not an object."));
     4199
     4200            emitLabel(finallyDone.get());
     4201            emitFinallyCompletion(*finallyContext, completionTypeRegister(), endCatchLabel.get());
     4202
     4203            popTry(returnCallTryData, finallyDone.get());
     4204
     4205            // Catch block for exceptions that may be thrown while calling the return
     4206            // handler in the enumeration finally block. The only reason we need this
     4207            // catch block is because if entered the above finally block due to a thrown
     4208            // exception, then we want to re-throw the original exception on exiting
     4209            // the finally block. Otherwise, we'll let any new exception pass through.
     4210            {
     4211                emitLabel(catchLabel.get());
     4212                RefPtr<RegisterID> exceptionRegister = newTemporary();
     4213                RegisterID* unused = newTemporary();
     4214                emitCatch(exceptionRegister.get(), unused);
     4215                // Since this is a synthesized catch block and we're guaranteed to never need
     4216                // to resolve any symbols from the scope, we can skip restoring the scope
     4217                // register here.
     4218
     4219                RefPtr<Label> throwLabel = newLabel();
     4220                emitJumpIfTrue(emitIsEmpty(newTemporary(), finallyExceptionRegister.get()), throwLabel.get());
     4221                emitMove(exceptionRegister.get(), finallyExceptionRegister.get());
     4222
     4223                emitLabel(throwLabel.get());
     4224                emitThrow(exceptionRegister.get());
     4225
     4226                emitLabel(endCatchLabel.get());
     4227            }
    43574228        }
    43584229
     
    43754246
    43764247    // IteratorClose sequence for break-ed control flow.
    4377     popIteratorCloseControlFlowScope();
     4248    popFinallyControlFlowScope();
    43784249    emitIteratorClose(iterator.get(), node);
    43794250    emitLabel(loopDone.get());
     
    44914362{
    44924363    emitOpcode(op_is_object);
     4364    instructions().append(dst->index());
     4365    instructions().append(src->index());
     4366    return dst;
     4367}
     4368
     4369RegisterID* BytecodeGenerator::emitIsNumber(RegisterID* dst, RegisterID* src)
     4370{
     4371    emitOpcode(op_is_number);
    44934372    instructions().append(dst->index());
    44944373    instructions().append(src->index());
     
    47834662    {
    47844663        RefPtr<RegisterID> returnRegister = generatorValueRegister();
    4785         if (isInFinallyBlock()) {
    4786             returnRegister = emitMove(newTemporary(), returnRegister.get());
    4787             emitPopScopes(scopeRegister(), 0);
    4788         }
    4789         emitReturn(returnRegister.get());
     4664        bool hasFinally = emitReturnViaFinallyIfNeeded(returnRegister.get());
     4665        if (!hasFinally)
     4666            emitReturn(returnRegister.get());
    47904667    }
    47914668
     
    48904767
    48914768                    emitLabel(returnSequence.get());
    4892                     if (isInFinallyBlock())
    4893                         emitPopScopes(scopeRegister(), 0);
    4894                     emitReturn(value.get());
     4769                    bool hasFinally = emitReturnViaFinallyIfNeeded(value.get());
     4770                    if (!hasFinally)
     4771                        emitReturn(value.get());
    48954772                }
    48964773
     
    49204797    RegisterID* completedState = emitLoad(nullptr, jsNumber(state));
    49214798    emitPutById(generatorRegister(), propertyNames().builtinNames().generatorStatePrivateName(), completedState);
     4799}
     4800
     4801bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget)
     4802{
     4803    ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
     4804    size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
     4805    ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
     4806    if (!scopeDelta)
     4807        return false; // No finallys to thread through.
     4808
     4809    ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
     4810    ControlFlowScope* bottomScope = &m_controlFlowScopeStack.last() - scopeDelta;
     4811
     4812    FinallyContext* innermostFinallyContext = nullptr;
     4813    FinallyContext* outermostFinallyContext = nullptr;
     4814    while (topScope > bottomScope) {
     4815        if (topScope->isFinallyScope()) {
     4816            FinallyContext* finallyContext = &topScope->finallyContext;
     4817            if (!innermostFinallyContext)
     4818                innermostFinallyContext = finallyContext;
     4819            outermostFinallyContext = finallyContext;
     4820            finallyContext->incNumberOfBreaksOrContinues();
     4821        }
     4822        --topScope;
     4823    }
     4824    if (!outermostFinallyContext)
     4825        return false; // No finallys to thread through.
     4826
     4827    auto jumpID = bytecodeOffsetToJumpID(instructions().size());
     4828    int lexicalScopeIndex = labelScopeDepthToLexicalScopeIndex(targetLabelScopeDepth);
     4829    outermostFinallyContext->registerJump(jumpID, lexicalScopeIndex, jumpTarget);
     4830
     4831    emitSetCompletionType(jumpID);
     4832    emitJump(innermostFinallyContext->finallyLabel());
     4833    return true; // We'll be jumping to a finally block.
     4834}
     4835
     4836bool BytecodeGenerator::emitReturnViaFinallyIfNeeded(RegisterID* returnRegister)
     4837{
     4838    if (!m_controlFlowScopeStack.size())
     4839        return false; // No finallys to thread through.
     4840
     4841    ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
     4842    ControlFlowScope* bottomScope = &m_controlFlowScopeStack.first();
     4843
     4844    FinallyContext* innermostFinallyContext = nullptr;
     4845    while (topScope >= bottomScope) {
     4846        if (topScope->isFinallyScope()) {
     4847            FinallyContext* finallyContext = &topScope->finallyContext;
     4848            if (!innermostFinallyContext)
     4849                innermostFinallyContext = finallyContext;
     4850            finallyContext->setHandlesReturns();
     4851        }
     4852        --topScope;
     4853    }
     4854    if (!innermostFinallyContext)
     4855        return false; // No finallys to thread through.
     4856
     4857    emitSetCompletionType(CompletionType::Return);
     4858    emitSetCompletionValue(returnRegister);
     4859    emitJump(innermostFinallyContext->finallyLabel());
     4860    return true; // We'll be jumping to a finally block.
     4861}
     4862
     4863void BytecodeGenerator::emitFinallyCompletion(FinallyContext& context, RegisterID* completionTypeRegister, Label* normalCompletionLabel)
     4864{
     4865    if (context.numberOfBreaksOrContinues() || context.handlesReturns()) {
     4866        emitJumpIf(op_stricteq, completionTypeRegister, CompletionType::Normal, normalCompletionLabel);
     4867
     4868        FinallyContext* outerContext = context.outerContext();
     4869
     4870        size_t numberOfJumps = context.numberOfJumps();
     4871        ASSERT(outerContext || numberOfJumps == context.numberOfBreaksOrContinues());
     4872
     4873        for (size_t i = 0; i < numberOfJumps; i++) {
     4874            RefPtr<Label> nextLabel = newLabel();
     4875            auto& jump = context.jumps(i);
     4876            emitJumpIf(op_nstricteq, completionTypeRegister, jump.jumpID, nextLabel.get());
     4877
     4878            restoreScopeRegister(jump.targetLexicalScopeIndex);
     4879            emitSetCompletionType(CompletionType::Normal);
     4880            emitJump(jump.targetLabel.get());
     4881
     4882            emitLabel(nextLabel.get());
     4883        }
     4884
     4885        if (outerContext) {
     4886            // We are not the outermost finally.
     4887            bool hasBreaksOrContinuesNotCoveredByJumps = context.numberOfBreaksOrContinues() > numberOfJumps;
     4888            if (hasBreaksOrContinuesNotCoveredByJumps || context.handlesReturns())
     4889                emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Throw, outerContext->finallyLabel());
     4890
     4891        } else {
     4892            // We are the outermost finally.
     4893            if (context.handlesReturns()) {
     4894                RefPtr<Label> notReturnLabel = newLabel();
     4895                emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Return, notReturnLabel.get());
     4896
     4897                emitWillLeaveCallFrameDebugHook();
     4898                emitReturn(completionValueRegister(), ReturnFrom::Finally);
     4899               
     4900                emitLabel(notReturnLabel.get());
     4901            }
     4902        }
     4903    }
     4904    emitJumpIf(op_nstricteq, completionTypeRegister, CompletionType::Throw, normalCompletionLabel);
     4905    emitThrow(completionValueRegister());
     4906}
     4907
     4908bool BytecodeGenerator::allocateCompletionRecordRegisters()
     4909{
     4910    if (m_completionTypeRegister)
     4911        return false;
     4912
     4913    ASSERT(!m_completionValueRegister);
     4914    m_completionTypeRegister = newTemporary();
     4915    m_completionValueRegister = newTemporary();
     4916
     4917    emitSetCompletionType(CompletionType::Normal);
     4918    emitMoveEmptyValue(m_completionValueRegister.get());
     4919    return true;
     4920}
     4921
     4922void BytecodeGenerator::releaseCompletionRecordRegisters()
     4923{
     4924    ASSERT(m_completionTypeRegister && m_completionValueRegister);
     4925    m_completionTypeRegister = nullptr;
     4926    m_completionValueRegister = nullptr;
     4927}
     4928
     4929void BytecodeGenerator::emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType type, Label* jumpTarget)
     4930{
     4931    RefPtr<RegisterID> tempRegister = newTemporary();
     4932    RegisterID* valueConstant = addConstantValue(jsNumber(static_cast<int>(type)));
     4933    OperandTypes operandTypes = OperandTypes(ResultType::numberTypeIsInt32(), ResultType::unknownType());
     4934
     4935    auto equivalenceResult = emitBinaryOp(compareOpcode, tempRegister.get(), valueConstant, completionTypeRegister, operandTypes);
     4936    emitJumpIfTrue(equivalenceResult, jumpTarget);
    49224937}
    49234938
  • trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h

    r210010 r210116  
    4545#include "UnlinkedCodeBlock.h"
    4646#include <functional>
     47#include <wtf/CheckedArithmetic.h>
    4748#include <wtf/HashTraits.h>
    4849#include <wtf/PassRefPtr.h>
     
    8182    };
    8283
     84    // https://tc39.github.io/ecma262/#sec-completion-record-specification-type
     85    //
     86    // For the Break and Continue cases, instead of using the Break and Continue enum values
     87    // below, we use the unique jumpID of the break and continue statement as the encoding
     88    // for the CompletionType value. emitFinallyCompletion() uses this jumpID value later
     89    // to determine the appropriate jump target to jump to after executing the relevant finally
     90    // blocks. The jumpID is computed as:
     91    //     jumpID = bytecodeOffset (of the break/continue node) + CompletionType::NumberOfTypes.
     92    // Hence, there won't be any collision between jumpIDs and CompletionType enums.
     93    enum class CompletionType : int {
     94        Normal,
     95        Break,
     96        Continue,
     97        Return,
     98        Throw,
     99       
     100        NumberOfTypes
     101    };
     102
     103    inline CompletionType bytecodeOffsetToJumpID(unsigned offset)
     104    {
     105        int jumpIDAsInt = offset + static_cast<int>(CompletionType::NumberOfTypes);
     106        ASSERT(jumpIDAsInt >= static_cast<int>(CompletionType::NumberOfTypes));
     107        return static_cast<CompletionType>(jumpIDAsInt);
     108    }
     109
     110    struct FinallyJump {
     111        FinallyJump(CompletionType jumpID, int targetLexicalScopeIndex, Label* targetLabel)
     112            : jumpID(jumpID)
     113            , targetLexicalScopeIndex(targetLexicalScopeIndex)
     114            , targetLabel(targetLabel)
     115        { }
     116
     117        CompletionType jumpID;
     118        int targetLexicalScopeIndex;
     119        RefPtr<Label> targetLabel;
     120    };
     121
    83122    struct FinallyContext {
    84         StatementNode* finallyBlock;
    85         RegisterID* iterator;
    86         ThrowableExpressionData* enumerationNode;
    87         unsigned controlFlowScopeStackSize;
    88         unsigned switchContextStackSize;
    89         unsigned forInContextStackSize;
    90         unsigned tryContextStackSize;
    91         unsigned labelScopesSize;
    92         unsigned lexicalScopeStackSize;
    93         int finallyDepth;
    94         int dynamicScopeDepth;
     123        FinallyContext() { }
     124        FinallyContext(FinallyContext* outerContext, Label* finallyLabel, int finallyDepth)
     125            : m_outerContext(outerContext)
     126            , m_finallyLabel(finallyLabel)
     127            , m_finallyDepth(finallyDepth)
     128        {
     129            ASSERT(m_finallyDepth >= 0);
     130            ASSERT(m_jumps.isEmpty());
     131        }
     132
     133        FinallyContext* outerContext() const { return m_outerContext; }
     134        Label* finallyLabel() const { return m_finallyLabel; }
     135        int depth() const { return m_finallyDepth; }
     136
     137        uint32_t numberOfBreaksOrContinues() const { return m_numberOfBreaksOrContinues.unsafeGet(); }
     138        void incNumberOfBreaksOrContinues() { m_numberOfBreaksOrContinues++; }
     139
     140        bool handlesReturns() const { return m_handlesReturns; }
     141        void setHandlesReturns() { m_handlesReturns = true; }
     142
     143        void registerJump(CompletionType jumpID, int lexicalScopeIndex, Label* targetLabel)
     144        {
     145            m_jumps.append(FinallyJump(jumpID, lexicalScopeIndex, targetLabel));
     146        }
     147
     148        size_t numberOfJumps() const { return m_jumps.size(); }
     149        FinallyJump& jumps(size_t i) { return m_jumps[i]; }
     150
     151    private:
     152        FinallyContext* m_outerContext { nullptr };
     153        Label* m_finallyLabel { nullptr };
     154        int m_finallyDepth { 0 };
     155        Checked<uint32_t, WTF::CrashOnOverflow> m_numberOfBreaksOrContinues;
     156        bool m_handlesReturns { false };
     157        Vector<FinallyJump> m_jumps;
    95158    };
    96159
    97160    struct ControlFlowScope {
    98         bool isFinallyBlock;
     161        typedef uint8_t Type;
     162        enum {
     163            Label,
     164            Finally
     165        };
     166        ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext&& finallyContext = FinallyContext())
     167            : type(type)
     168            , lexicalScopeIndex(lexicalScopeIndex)
     169            , finallyContext(std::forward<FinallyContext>(finallyContext))
     170        { }
     171
     172        bool isLabelScope() const { return type == Label; }
     173        bool isFinallyScope() const { return type == Finally; }
     174
     175        Type type;
     176        int lexicalScopeIndex;
    99177        FinallyContext finallyContext;
    100178    };
     
    606684        RegisterID* emitGetTemplateObject(RegisterID* dst, TaggedTemplateNode*);
    607685
    608         RegisterID* emitReturn(RegisterID* src);
     686        enum class ReturnFrom { Normal, Finally };
     687        RegisterID* emitReturn(RegisterID* src, ReturnFrom = ReturnFrom::Normal);
    609688        RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
    610689
     
    627706        PassRefPtr<Label> emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
    628707        PassRefPtr<Label> emitJumpIfNotFunctionApply(RegisterID* cond, Label* target);
    629         void emitPopScopes(RegisterID* srcDst, int targetScopeDepth);
    630708
    631709        void emitEnter();
     
    650728        RegisterID* emitIsSet(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSSetType); }
    651729        RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
     730        RegisterID* emitIsNumber(RegisterID* dst, RegisterID* src);
    652731        RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
    653732        RegisterID* emitIsEmpty(RegisterID* dst, RegisterID* src);
     
    664743
    665744        // Start a try block. 'start' must have been emitted.
    666         TryData* pushTry(Label* start);
     745        TryData* pushTry(Label* start, Label* handlerLabel, HandlerType);
    667746        // End a try block. 'end' must have been emitted.
    668         void popTryAndEmitCatch(TryData*, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType);
     747        void popTry(TryData*, Label* end);
     748        void emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister);
     749
     750    private:
     751        static const int CurrentLexicalScopeIndex = -2;
     752        static const int OutermostLexicalScopeIndex = -1;
     753
     754        int currentLexicalScopeIndex() const
     755        {
     756            int size = static_cast<int>(m_lexicalScopeStack.size());
     757            ASSERT(static_cast<size_t>(size) == m_lexicalScopeStack.size());
     758            ASSERT(size >= 0);
     759            if (!size)
     760                return OutermostLexicalScopeIndex;
     761            return size - 1;
     762        }
     763
     764    public:
     765        void restoreScopeRegister();
     766        void restoreScopeRegister(int lexicalScopeIndex);
     767
     768        int labelScopeDepthToLexicalScopeIndex(int labelScopeDepth);
    669769
    670770        void emitThrow(RegisterID* exc)
     
    699799        void emitWillLeaveCallFrameDebugHook();
    700800
    701         bool isInFinallyBlock() { return m_finallyDepth > 0; }
    702 
    703         void pushFinallyControlFlowScope(StatementNode* finallyBlock);
    704         void popFinallyControlFlowScope();
    705         void pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* enumerationNode);
    706         void popIteratorCloseControlFlowScope();
     801        class CompletionRecordScope {
     802        public:
     803            CompletionRecordScope(BytecodeGenerator& generator, bool needCompletionRecordRegisters = true)
     804                : m_generator(generator)
     805            {
     806                if (needCompletionRecordRegisters && m_generator.allocateCompletionRecordRegisters())
     807                    m_needToReleaseOnDestruction = true;
     808            }
     809            ~CompletionRecordScope()
     810            {
     811                if (m_needToReleaseOnDestruction)
     812                    m_generator.releaseCompletionRecordRegisters();
     813            }
     814
     815        private:
     816            BytecodeGenerator& m_generator;
     817            bool m_needToReleaseOnDestruction { false };
     818        };
     819
     820        RegisterID* completionTypeRegister() const
     821        {
     822            ASSERT(m_completionTypeRegister);
     823            return m_completionTypeRegister.get();
     824        }
     825        RegisterID* completionValueRegister() const
     826        {
     827            ASSERT(m_completionValueRegister);
     828            return m_completionValueRegister.get();
     829        }
     830
     831        void emitSetCompletionType(CompletionType type)
     832        {
     833            emitLoad(completionTypeRegister(), JSValue(static_cast<int>(type)));
     834        }
     835        void emitSetCompletionValue(RegisterID* reg)
     836        {
     837            emitMove(completionValueRegister(), reg);
     838        }
     839
     840        void emitJumpIf(OpcodeID compareOpcode, RegisterID* completionTypeRegister, CompletionType, Label* jumpTarget);
     841
     842        bool emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget);
     843        bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
     844        void emitFinallyCompletion(FinallyContext&, RegisterID* completionTypeRegister, Label* normalCompletionLabel);
     845
     846    private:
     847        bool allocateCompletionRecordRegisters();
     848        void releaseCompletionRecordRegisters();
     849
     850    public:
     851        FinallyContext* pushFinallyControlFlowScope(Label* finallyLabel);
     852        FinallyContext popFinallyControlFlowScope();
    707853
    708854        void pushIndexedForInScope(RegisterID* local, RegisterID* index);
     
    798944        void allocateCalleeSaveSpace();
    799945        void allocateAndEmitScope();
    800         void emitComplexPopScopes(RegisterID*, ControlFlowScope* topScope, ControlFlowScope* bottomScope);
    801946
    802947        typedef HashMap<double, JSValue> NumberMap;
     
    9361081        RegisterID* m_promiseCapabilityRegister { nullptr };
    9371082
     1083        RefPtr<RegisterID> m_completionTypeRegister;
     1084        RefPtr<RegisterID> m_completionValueRegister;
     1085
     1086        FinallyContext* m_currentFinallyContext { nullptr };
     1087
    9381088        SegmentedVector<RegisterID*, 16> m_localRegistersForCalleeSaveRegisters;
    9391089        SegmentedVector<RegisterID, 32> m_constantPoolRegisters;
     
    9501100        void popLocalControlFlowScope();
    9511101
    952         Vector<ControlFlowScope, 0, UnsafeVectorOverflow> m_controlFlowScopeStack;
     1102        // FIXME: Restore overflow checking with UnsafeVectorOverflow once SegmentVector supports it.
     1103        // https://bugs.webkit.org/show_bug.cgi?id=165980
     1104        SegmentedVector<ControlFlowScope, 16> m_controlFlowScopeStack;
    9531105        Vector<SwitchInfo> m_switchContextStack;
    9541106        Vector<RefPtr<ForInContext>> m_forInContextStack;
  • trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

    r210023 r210116  
    29992999    ASSERT(scope);
    30003000
    3001     generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
    3002     generator.emitJump(scope->continueTarget());
     3001    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->continueTarget());
     3002    if (!hasFinally) {
     3003        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
     3004        generator.restoreScopeRegister(lexicalScopeIndex);
     3005        generator.emitJump(scope->continueTarget());
     3006    }
    30033007
    30043008    generator.emitProfileControlFlow(endOffset());
     
    30263030    ASSERT(scope);
    30273031
    3028     generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
    3029     generator.emitJump(scope->breakTarget());
     3032    bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->breakTarget());
     3033    if (!hasFinally) {
     3034        int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
     3035        generator.restoreScopeRegister(lexicalScopeIndex);
     3036        generator.emitJump(scope->breakTarget());
     3037    }
    30303038
    30313039    generator.emitProfileControlFlow(endOffset());
     
    30443052
    30453053    generator.emitProfileType(returnRegister.get(), ProfileTypeBytecodeFunctionReturnStatement, divotStart(), divotEnd());
    3046     if (generator.isInFinallyBlock()) {
    3047         returnRegister = generator.emitMove(generator.newTemporary(), returnRegister.get());
    3048         generator.emitPopScopes(generator.scopeRegister(), 0);
    3049     }
    3050 
    3051     generator.emitWillLeaveCallFrameDebugHook();
    3052     generator.emitReturn(returnRegister.get());
    3053     generator.emitProfileControlFlow(endOffset()); 
     3054
     3055    bool hasFinally = generator.emitReturnViaFinallyIfNeeded(returnRegister.get());
     3056    if (!hasFinally) {
     3057        generator.emitWillLeaveCallFrameDebugHook();
     3058        generator.emitReturn(returnRegister.get());
     3059    }
     3060
     3061    generator.emitProfileControlFlow(endOffset());
    30543062    // Emitting an unreachable return here is needed in case this op_profile_control_flow is the
    30553063    // last opcode in a CodeBlock because a CodeBlock's instructions must end with a terminal opcode.
     
    32803288
    32813289    ASSERT(m_catchBlock || m_finallyBlock);
     3290    BytecodeGenerator::CompletionRecordScope completionRecordScope(generator, m_finallyBlock);
     3291
     3292    RefPtr<Label> catchLabel;
     3293    RefPtr<Label> catchEndLabel;
     3294    RefPtr<Label> finallyViaThrowLabel;
     3295    RefPtr<Label> finallyLabel;
     3296    RefPtr<Label> finallyEndLabel;
    32823297
    32833298    RefPtr<Label> tryStartLabel = generator.newLabel();
    32843299    generator.emitLabel(tryStartLabel.get());
    3285    
     3300
     3301    if (m_finallyBlock) {
     3302        finallyViaThrowLabel = generator.newLabel();
     3303        finallyLabel = generator.newLabel();
     3304        finallyEndLabel = generator.newLabel();
     3305
     3306        generator.pushFinallyControlFlowScope(finallyLabel.get());
     3307    }
     3308    if (m_catchBlock) {
     3309        catchLabel = generator.newLabel();
     3310        catchEndLabel = generator.newLabel();
     3311    }
     3312
     3313    Label* tryHandlerLabel = m_catchBlock ? catchLabel.get() : finallyViaThrowLabel.get();
     3314    HandlerType tryHandlerType = m_catchBlock ? HandlerType::Catch : HandlerType::Finally;
     3315    TryData* tryData = generator.pushTry(tryStartLabel.get(), tryHandlerLabel, tryHandlerType);
     3316
     3317    generator.emitNode(dst, m_tryBlock);
     3318
    32863319    if (m_finallyBlock)
    3287         generator.pushFinallyControlFlowScope(m_finallyBlock);
    3288     TryData* tryData = generator.pushTry(tryStartLabel.get());
    3289 
    3290     generator.emitNode(dst, m_tryBlock);
     3320        generator.emitJump(finallyLabel.get());
     3321    else
     3322        generator.emitJump(catchEndLabel.get());
     3323
     3324    RefPtr<Label> endTryLabel = generator.emitLabel(generator.newLabel().get());
     3325    generator.popTry(tryData, endTryLabel.get());
    32913326
    32923327    if (m_catchBlock) {
    3293         RefPtr<Label> catchEndLabel = generator.newLabel();
    3294        
    3295         // Normal path: jump over the catch block.
    3296         generator.emitJump(catchEndLabel.get());
    3297 
    32983328        // Uncaught exception path: the catch block.
    3299         RefPtr<Label> here = generator.emitLabel(generator.newLabel().get());
    3300         RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
     3329        generator.emitLabel(catchLabel.get());
    33013330        RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
    3302         generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), here.get(), HandlerType::Catch);
    3303        
     3331        RegisterID* unused = generator.newTemporary();
     3332        generator.emitCatch(unused, thrownValueRegister.get());
     3333        generator.restoreScopeRegister();
     3334
     3335        TryData* tryData = nullptr;
    33043336        if (m_finallyBlock) {
    33053337            // If the catch block throws an exception and we have a finally block, then the finally
    33063338            // block should "catch" that exception.
    3307             tryData = generator.pushTry(here.get());
     3339            tryData = generator.pushTry(catchLabel.get(), finallyViaThrowLabel.get(), HandlerType::Finally);
    33083340        }
    33093341
     
    33173349        generator.emitLoad(thrownValueRegister.get(), jsUndefined());
    33183350        generator.emitPopCatchScope(m_lexicalVariables);
     3351
     3352        if (m_finallyBlock) {
     3353            generator.emitSetCompletionType(CompletionType::Normal);
     3354            generator.emitJump(finallyLabel.get());
     3355            generator.popTry(tryData, finallyViaThrowLabel.get());
     3356        }
     3357
    33193358        generator.emitLabel(catchEndLabel.get());
     3359        generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
    33203360    }
    33213361
    33223362    if (m_finallyBlock) {
    3323         RefPtr<Label> preFinallyLabel = generator.emitLabel(generator.newLabel().get());
    3324        
    3325         generator.popFinallyControlFlowScope();
    3326 
    3327         RefPtr<Label> finallyEndLabel = generator.newLabel();
     3363        FinallyContext finallyContext = generator.popFinallyControlFlowScope();
     3364
     3365        // Entry to the finally block for CompletionType::Throw.
     3366        generator.emitLabel(finallyViaThrowLabel.get());
     3367        RegisterID* unused = generator.newTemporary();
     3368        generator.emitCatch(generator.completionValueRegister(), unused);
     3369        generator.emitSetCompletionType(CompletionType::Throw);
     3370
     3371        // Entry to the finally block for CompletionTypes other than Throw.
     3372        generator.emitLabel(finallyLabel.get());
     3373        generator.restoreScopeRegister();
     3374
     3375        RefPtr<RegisterID> savedCompletionTypeRegister = generator.newTemporary();
     3376        generator.emitMove(savedCompletionTypeRegister.get(), generator.completionTypeRegister());
    33283377
    33293378        int finallyStartOffset = m_catchBlock ? m_catchBlock->endOffset() + 1 : m_tryBlock->endOffset() + 1;
    3330 
    3331         // Normal path: run the finally code, and jump to the end.
    33323379        generator.emitProfileControlFlow(finallyStartOffset);
    33333380        generator.emitNodeInTailPosition(dst, m_finallyBlock);
    3334         generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
    3335         generator.emitJump(finallyEndLabel.get());
    3336 
    3337         // Uncaught exception path: invoke the finally block, then re-throw the exception.
    3338         RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
    3339         RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
    3340         generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), preFinallyLabel.get(), HandlerType::Finally);
    3341         generator.emitProfileControlFlow(finallyStartOffset);
    3342         generator.emitNodeInTailPosition(dst, m_finallyBlock);
    3343         generator.emitThrow(exceptionRegister.get());
    3344 
     3381
     3382        generator.emitFinallyCompletion(finallyContext, savedCompletionTypeRegister.get(), finallyEndLabel.get());
    33453383        generator.emitLabel(finallyEndLabel.get());
    33463384        generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
    3347     } else
    3348         generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
    3349 
     3385    }
    33503386}
    33513387
  • trunk/Source/WTF/ChangeLog

    r210010 r210116  
     12016-12-22  Mark Lam  <mark.lam@apple.com>
     2
     3        De-duplicate finally blocks.
     4        https://bugs.webkit.org/show_bug.cgi?id=160168
     5
     6        Reviewed by Saam Barati.
     7
     8        Added some methods to bring SegmentedVector closer to parity with Vector.
     9
     10        * wtf/SegmentedVector.h:
     11        (WTF::SegmentedVector::first):
     12        (WTF::SegmentedVector::last):
     13        (WTF::SegmentedVector::takeLast):
     14
    1152016-12-19  Mark Lam  <mark.lam@apple.com>
    216
  • trunk/Source/WTF/wtf/SegmentedVector.h

    r210010 r210116  
    128128        }
    129129
     130        T& first()
     131        {
     132            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
     133            return at(0);
     134        }
     135        const T& first() const
     136        {
     137            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
     138            return at(0);
     139        }
    130140        T& last()
    131141        {
     142            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
    132143            return at(size() - 1);
     144        }
     145        const T& last() const
     146        {
     147            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
     148            return at(size() - 1);
     149        }
     150
     151        T takeLast()
     152        {
     153            ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
     154            T result = WTFMove(last());
     155            --m_size;
     156            return result;
    133157        }
    134158
Note: See TracChangeset for help on using the changeset viewer.