Skip to content

Commit 5da354f

Browse files
osa1Commit Queue
authored andcommitted
[dart2wasm] Don't compile catch blocks multiple times for JS exceptions
Currently when a Dart `catch` block can catch both Dart and JS exceptions, we compile the `catch` body once in a Wasm `catch` block, once in a Wasm `catch_all` block. This is because a Dart exception needs to be caught in a `catch` with the right tag, to be able to get the exception and stack trace values, and JS exceptions need to be caught in `catch_all` and they come without error values and stack traces. With this CL we generate one Wasm block per Dart `catch` block. Wasm `catch` and `catch_all` blocks only do type tests and jump to the right Wasm `block` when a type test passes. This allows using the same block for multiple Wasm `catch` and `catch_all` blocks. When jumping to the block for a Dart `catch` we pass the error value and stack trace to the block. As before, when the caught exception is a JS exception, we pass an empty `JavaScriptError` as the error value and the call stack of the Dart `catch` as the stack trace. We also replace Wasm `rethrow` instruction with `throw` when compiling Dart `rethrow` statements. This change is necessary as the blocks for Dart `catch` blocks are no longer enclosed by a Wasm `try`, and it also makes it easier to switch to the new exception handling proposal, which doesn't have a `rethrow` instruction. This changes Wasm exceptions reported to the console in uncaught exceptions, but when we switch to the new exception handling instructions we will recover the stack traces, as `throw_ref` doesn't update the stack trace of the error value. Change-Id: I732c0192af158611d5f0a584182a48b0e13ff83a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410321 Commit-Queue: Ömer Ağacan <[email protected]> Reviewed-by: Martin Kustermann <[email protected]>
1 parent 26f08dd commit 5da354f

File tree

1 file changed

+88
-50
lines changed

1 file changed

+88
-50
lines changed

pkg/dart2wasm/lib/code_generator.dart

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ abstract class AstCodeGenerator
9090
final LinkedHashMap<LabeledStatement, List<w.Label>> breakFinalizers =
9191
LinkedHashMap();
9292

93-
final List<w.Label> tryLabels = [];
93+
final List<({w.Local exceptionLocal, w.Local stackTraceLocal})>
94+
tryBlockLocals = [];
9495

9596
final Map<SwitchCase, w.Label> switchLabels = {};
9697

@@ -903,33 +904,48 @@ abstract class AstCodeGenerator
903904

904905
@override
905906
void visitTryCatch(TryCatch node) {
906-
// It is not valid dart to have a try without a catch.
907+
// It is not valid Dart to have a try without a catch.
907908
assert(node.catches.isNotEmpty);
908909

909-
// We lower a [TryCatch] to a wasm try block.
910-
w.Label try_ = b.try_();
911-
translateStatement(node.body);
912-
b.br(try_);
910+
final w.RefType exceptionType = translator.topInfo.nonNullableType;
911+
final w.RefType stackTraceType =
912+
translator.stackTraceInfo.repr.nonNullableType;
913+
914+
final w.Label wrapperBlock = b.block();
915+
916+
// Create a block target for each Dart `catch` block, to be able to share
917+
// code when generating a `catch` and `catch_all` for the same Dart `catch`
918+
// block, when the block can catch both Dart and JS exceptions.
919+
// The `end` for the Wasm `try` block works as the first exception handler
920+
// target.
921+
List<w.Label> catchBlockLabels = List.generate(node.catches.length - 1,
922+
(i) => b.block([], [exceptionType, stackTraceType]),
923+
growable: true);
924+
925+
w.Label try_ = b.try_([], [exceptionType, stackTraceType]);
926+
catchBlockLabels.add(try_);
913927

914-
// Note: We must wait to add the try block to the [tryLabels] stack until
915-
// after we have visited the body of the try. This is to handle the case of
916-
// a rethrow nested within a try nested within a catch, that is we need the
917-
// rethrow to target the last try block with a catch.
918-
tryLabels.add(try_);
928+
catchBlockLabels = catchBlockLabels.reversed.toList();
929+
930+
translateStatement(node.body);
931+
b.br(wrapperBlock);
919932

920933
// Stash the original exception in a local so we can push it back onto the
921934
// stack after each type test. Also, store the stack trace in a local.
922-
w.Local thrownException = addLocal(translator.topInfo.nonNullableType);
923-
w.Local thrownStackTrace =
924-
addLocal(translator.stackTraceInfo.repr.nonNullableType);
935+
w.Local thrownException = addLocal(exceptionType);
936+
w.Local thrownStackTrace = addLocal(stackTraceType);
937+
938+
tryBlockLocals.add(
939+
(exceptionLocal: thrownException, stackTraceLocal: thrownStackTrace));
925940

926-
void emitCatchBlock(Catch catch_, bool emitGuard) {
941+
void emitCatchBlock(
942+
w.Label catchBlockTarget, Catch catch_, bool emitGuard) {
927943
// For each catch node:
928944
// 1) Create a block for the catch.
929945
// 2) Push the caught exception onto the stack.
930946
// 3) Add a type test based on the guard of the catch.
931947
// 4) If the test fails, we jump to the next catch. Otherwise, we
932-
// execute the body of the catch.
948+
// jump to the block for the body of the catch.
933949
w.Label catchBlock = b.block();
934950
DartType guard = catch_.guard;
935951

@@ -942,29 +958,10 @@ abstract class AstCodeGenerator
942958
b.br_if(catchBlock);
943959
}
944960

945-
final VariableDeclaration? exceptionDeclaration = catch_.exception;
946-
if (exceptionDeclaration != null) {
947-
initializeVariable(exceptionDeclaration, () {
948-
b.local_get(thrownException);
949-
// Type test passed, downcast the exception to the expected type.
950-
translator.convertType(
951-
b,
952-
thrownException.type,
953-
translator.translateType(exceptionDeclaration.type),
954-
);
955-
});
956-
}
957-
958-
final VariableDeclaration? stackTraceDeclaration = catch_.stackTrace;
959-
if (stackTraceDeclaration != null) {
960-
initializeVariable(
961-
stackTraceDeclaration, () => b.local_get(thrownStackTrace));
962-
}
963-
964-
translateStatement(catch_.body);
961+
b.local_get(thrownException);
962+
b.local_get(thrownStackTrace);
963+
b.br(catchBlockTarget);
965964

966-
// Jump out of the try entirely if we enter any catch block.
967-
b.br(try_);
968965
b.end(); // end catchBlock.
969966
}
970967

@@ -974,26 +971,29 @@ abstract class AstCodeGenerator
974971

975972
b.local_set(thrownStackTrace);
976973
b.local_set(thrownException);
977-
for (final Catch catch_ in node.catches) {
974+
for (int catchBlockIndex = 0;
975+
catchBlockIndex < node.catches.length;
976+
catchBlockIndex += 1) {
977+
final catch_ = node.catches[catchBlockIndex];
978978
// Only insert type checks if the guard is not `Object`
979979
final bool shouldEmitGuard =
980980
catch_.guard != translator.coreTypes.objectNonNullableRawType;
981-
emitCatchBlock(catch_, shouldEmitGuard);
981+
emitCatchBlock(
982+
catchBlockLabels[catchBlockIndex], catch_, shouldEmitGuard);
982983
if (!shouldEmitGuard) {
983984
// If we didn't emit a guard, we won't ever fall through to the
984985
// following catch blocks.
985986
break;
986987
}
987988
}
989+
988990
// Rethrow if all the catch blocks fall through
989991
b.rethrow_(try_);
990992

991993
// If we have a catches that are generic enough to catch a JavaScript
992994
// error, we need to put that into a catch_all block.
993-
final Iterable<Catch> catchAllCatches = node.catches
994-
.where((c) => guardCanMatchJSException(translator, c.guard));
995-
996-
if (catchAllCatches.isNotEmpty) {
995+
if (node.catches
996+
.any((c) => guardCanMatchJSException(translator, c.guard))) {
997997
// This catches any objects that aren't dart exceptions, such as
998998
// JavaScript exceptions or objects.
999999
b.catch_all();
@@ -1007,14 +1007,21 @@ abstract class AstCodeGenerator
10071007
call(translator.javaScriptErrorFactory.reference);
10081008
b.local_set(thrownException);
10091009

1010-
for (final c in catchAllCatches) {
1010+
for (int catchBlockIndex = 0;
1011+
catchBlockIndex < node.catches.length;
1012+
catchBlockIndex += 1) {
1013+
final catch_ = node.catches[catchBlockIndex];
1014+
if (!guardCanMatchJSException(translator, catch_.guard)) {
1015+
continue;
1016+
}
10111017
// Type guards based on a type parameter are special, in that we cannot
10121018
// statically determine whether a JavaScript error will always satisfy
10131019
// the guard, so we should emit the type checking code for it. All
10141020
// other guards will always match a JavaScript error, however, so no
10151021
// need to emit type checks for those.
1016-
final bool shouldEmitGuard = c.guard is TypeParameterType;
1017-
emitCatchBlock(c, shouldEmitGuard);
1022+
final bool shouldEmitGuard = catch_.guard is TypeParameterType;
1023+
emitCatchBlock(
1024+
catchBlockLabels[catchBlockIndex], catch_, shouldEmitGuard);
10181025
if (!shouldEmitGuard) {
10191026
// If we didn't emit a guard, we won't ever fall through to the
10201027
// following catch blocks.
@@ -1026,8 +1033,36 @@ abstract class AstCodeGenerator
10261033
b.rethrow_(try_);
10271034
}
10281035

1029-
tryLabels.removeLast();
1030-
b.end(); // end try_.
1036+
for (Catch catch_ in node.catches) {
1037+
b.end();
1038+
b.local_set(thrownStackTrace);
1039+
b.local_set(thrownException);
1040+
1041+
final VariableDeclaration? exceptionDeclaration = catch_.exception;
1042+
if (exceptionDeclaration != null) {
1043+
initializeVariable(exceptionDeclaration, () {
1044+
b.local_get(thrownException);
1045+
// Type test passed, downcast the exception to the expected type.
1046+
translator.convertType(
1047+
b,
1048+
thrownException.type,
1049+
translator.translateType(exceptionDeclaration.type),
1050+
);
1051+
});
1052+
}
1053+
1054+
final VariableDeclaration? stackTraceDeclaration = catch_.stackTrace;
1055+
if (stackTraceDeclaration != null) {
1056+
initializeVariable(
1057+
stackTraceDeclaration, () => b.local_get(thrownStackTrace));
1058+
}
1059+
1060+
translateStatement(catch_.body);
1061+
b.br(wrapperBlock);
1062+
}
1063+
1064+
tryBlockLocals.removeLast();
1065+
b.end(); // end tryWrapper
10311066
}
10321067

10331068
@override
@@ -2721,7 +2756,10 @@ abstract class AstCodeGenerator
27212756

27222757
@override
27232758
w.ValueType visitRethrow(Rethrow node, w.ValueType expectedType) {
2724-
b.rethrow_(tryLabels.last);
2759+
final exceptionLocals = tryBlockLocals.last;
2760+
b.local_get(exceptionLocals.exceptionLocal);
2761+
b.local_get(exceptionLocals.stackTraceLocal);
2762+
b.throw_(translator.getExceptionTag(b.module));
27252763
return expectedType;
27262764
}
27272765

0 commit comments

Comments
 (0)