[dart2wasm] Run finally blocks on break and continue

Fixes https://github.com/dart-lang/sdk/issues/50026

No new tests added: existing tests cover this. These tests now pass:

co19/Language/Statements/Break/execution_t01
co19/Language/Statements/Break/execution_t02
co19/Language/Statements/Break/execution_t04
co19/Language/Statements/Break/execution_t06
co19/Language/Statements/Break/execution_t07
co19/Language/Statements/Break/execution_t08
co19/Language/Statements/Break/execution_t09
co19/Language/Statements/Continue/control_transfer_t01
co19/Language/Statements/Continue/control_transfer_t02
co19/Language/Statements/Continue/control_transfer_t03
co19/Language/Statements/Continue/control_transfer_t04
co19/Language/Statements/Continue/control_transfer_t05
co19/Language/Statements/Continue/control_transfer_t07
co19/Language/Statements/Continue/control_transfer_t08
co19/Language/Statements/Continue/control_transfer_t09
language/exception/finally12_test
language/exception/finally3_test
language/exception/finally9_test
language/exception/try_catch4_test
language/exception/try_finally_regress_25654_test

Change-Id: I7172d7dac9e58d37c2ecfbc1bde495d8d045fc74
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262240
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Ömer Sinan Ağacan 2022-10-04 11:23:24 +00:00 committed by Commit Queue
parent 88539e58ea
commit d968be9021

View file

@ -53,9 +53,17 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.Local? preciseThisLocal; w.Local? preciseThisLocal;
w.Local? returnValueLocal; w.Local? returnValueLocal;
final Map<TypeParameter, w.Local> typeLocals = {}; final Map<TypeParameter, w.Local> typeLocals = {};
final List<TryBlockFinalizer> finalizers = [];
/// Finalizers to run on `return`.
final List<TryBlockFinalizer> returnFinalizers = [];
/// Finalizers to run on a `break`. `breakFinalizers[L].last` (which should
/// always be present) is the `br` target for the label `L` that will run the
/// finalizers, or break out of the loop.
final Map<LabeledStatement, List<w.Label>> breakFinalizers = {};
final List<w.Label> tryLabels = []; final List<w.Label> tryLabels = [];
final Map<LabeledStatement, w.Label> labels = {};
final Map<SwitchCase, w.Label> switchLabels = {}; final Map<SwitchCase, w.Label> switchLabels = {};
/// Maps a switch statement to the information used when doing a backward /// Maps a switch statement to the information used when doing a backward
@ -726,15 +734,15 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override @override
void visitLabeledStatement(LabeledStatement node) { void visitLabeledStatement(LabeledStatement node) {
w.Label label = b.block(); w.Label label = b.block();
labels[node] = label; breakFinalizers[node] = <w.Label>[label];
visitStatement(node.body); visitStatement(node.body);
labels.remove(node); breakFinalizers.remove(node);
b.end(); b.end();
} }
@override @override
void visitBreakStatement(BreakStatement node) { void visitBreakStatement(BreakStatement node) {
b.br(labels[node.target]!); b.br(breakFinalizers[node.target]!.last);
} }
@override @override
@ -867,30 +875,64 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override @override
void visitTryFinally(TryFinally node) { void visitTryFinally(TryFinally node) {
// We lower a [TryFinally] to three nested blocks, and we emit the finalizer // We lower a [TryFinally] to a number of nested blocks, depending on how
// up to three times. Once in a catch, to handle the case where the try // many different code paths we have that run the finally block.
//
// We emit the finalizer once in a catch, to handle the case where the try
// throws. Once outside of the catch, to handle the case where the try does // throws. Once outside of the catch, to handle the case where the try does
// not throw. Finally, if there is a return within the try block, then we // not throw. If there is a return within the try block, then we emit the
// emit the finalizer one more time along with logic to continue walking up // finalizer one more time along with logic to continue walking up the
// the stack. // stack.
//
// A `break L` can run more than one finalizer, and each of those
// finalizers will need to be run in a different `try` block. So for each
// wrapping label we generate a block to run the finalizer on `break` and
// then branch to the right Wasm block to either run the next finalizer or
// break.
// The block for the try-finally statement. Used as `br` target in normal
// execution after the finalizer (no throws, returns, or breaks).
w.Label tryFinallyBlock = b.block(); w.Label tryFinallyBlock = b.block();
w.Label finalizerBlock = b.block();
finalizers.add(TryBlockFinalizer(finalizerBlock)); // Create one block for each wrapping label
for (final labelBlocks in breakFinalizers.values) {
labelBlocks.add(b.block());
}
// Continuation of this block runs the finalizer and returns (or jumps to
// the next finalizer block). Used as `br` target on `return`.
w.Label returnFinalizerBlock = b.block();
returnFinalizers.add(TryBlockFinalizer(returnFinalizerBlock));
w.Label tryBlock = b.try_(); w.Label tryBlock = b.try_();
visitStatement(node.body); visitStatement(node.body);
bool mustHandleReturn = finalizers.removeLast().mustHandleReturn; final bool mustHandleReturn =
b.br(tryBlock); returnFinalizers.removeLast().mustHandleReturn;
b.catch_(translator.exceptionTag); b.catch_(translator.exceptionTag);
// `break` statements in the current finalizer and the rest will not run
// the current finalizer, update the `break` targets
final removedBreakTargets = <LabeledStatement, w.Label>{};
for (final breakFinalizerEntry in breakFinalizers.entries) {
removedBreakTargets[breakFinalizerEntry.key] =
breakFinalizerEntry.value.removeLast();
}
// Run finalizer on exception
visitStatement(node.finalizer); visitStatement(node.finalizer);
b.rethrow_(tryBlock); b.rethrow_(tryBlock);
b.end(); // end tryBlock. b.end(); // end tryBlock.
// Run finalizer on normal execution (no breaks, throws, or returns)
visitStatement(node.finalizer); visitStatement(node.finalizer);
b.br(tryFinallyBlock); b.br(tryFinallyBlock);
b.end(); // end finalizerBlock. b.end(); // end returnFinalizerBlock.
// Run finalizer on `return`
if (mustHandleReturn) { if (mustHandleReturn) {
visitStatement(node.finalizer); visitStatement(node.finalizer);
if (finalizers.isNotEmpty) { if (returnFinalizers.isNotEmpty) {
b.br(finalizers.last.label); b.br(returnFinalizers.last.label);
} else { } else {
if (returnValueLocal != null) { if (returnValueLocal != null) {
b.local_get(returnValueLocal!); b.local_get(returnValueLocal!);
@ -899,7 +941,16 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
_returnFromFunction(); _returnFromFunction();
} }
} }
b.end(); // end tryFinallyBlock.
// Generate finalizers for `break`s in the `try` block
for (final removedBreakTargetEntry in removedBreakTargets.entries) {
b.end();
visitStatement(node.finalizer);
b.br(breakFinalizers[removedBreakTargetEntry.key]!.last);
}
// Terminate `tryFinallyBlock`
b.end();
} }
@override @override
@ -1077,8 +1128,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
// as the stack unwinds. When we get to the top of the finalizer stack, we // as the stack unwinds. When we get to the top of the finalizer stack, we
// will handle the return using [returnValueLocal] if this function returns // will handle the return using [returnValueLocal] if this function returns
// a value. // a value.
if (finalizers.isNotEmpty) { if (returnFinalizers.isNotEmpty) {
for (TryBlockFinalizer finalizer in finalizers) { for (TryBlockFinalizer finalizer in returnFinalizers) {
finalizer.mustHandleReturn = true; finalizer.mustHandleReturn = true;
} }
if (returnType != voidMarker) { if (returnType != voidMarker) {
@ -1088,7 +1139,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
returnValueLocal ??= addLocal(returnType.withNullability(true)); returnValueLocal ??= addLocal(returnType.withNullability(true));
b.local_set(returnValueLocal!); b.local_set(returnValueLocal!);
} }
b.br(finalizers.last.label); b.br(returnFinalizers.last.label);
} else { } else {
_returnFromFunction(); _returnFromFunction();
} }
@ -2411,7 +2462,12 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
} }
class TryBlockFinalizer { class TryBlockFinalizer {
/// `br` target to run the finalizer
final w.Label label; final w.Label label;
/// Whether the last finalizer in the chain should return. When this is
/// `false` the block won't be used, as the block is for running finalizers
/// when returning.
bool mustHandleReturn = false; bool mustHandleReturn = false;
TryBlockFinalizer(this.label); TryBlockFinalizer(this.label);