[dart2wasm] Handle exceptions and returns in async* functions

New passing tests:

- co19/Language/Expressions/Function_Invocation/async_cleanup_t07
- co19/Language/Expressions/Function_Invocation/async_cleanup_t08
- co19/Language/Expressions/Function_Invocation/async_generator_invokation_t05
- co19/Language/Expressions/Function_Invocation/async_generator_invokation_t09
- co19/Language/Statements/Rethrow/execution_t04
- co19/Language/Statements/Return/syntax_t10
- co19/Language/Statements/Return/syntax_t11
- co19/Language/Statements/Return/syntax_t12
- co19/Language/Statements/Return/syntax_t13
- co19/Language/Statements/Yield_and_Yield_Each/Yield_Each/execution_async_A01_t01
- language/async/return_types_runtime_test
- language/async_star/basic_test
- language/async_star/yield_statement_context_test
- lib/async/stream_from_iterable_test

Change-Id: Id03cc0abe150dadfcd753c4e74a282d46260a1f8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/304501
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Ömer Sinan Ağacan 2023-05-22 20:31:38 +00:00 committed by Commit Queue
parent 1e1258e595
commit 9d1dd89768
3 changed files with 125 additions and 63 deletions

View file

@ -1068,7 +1068,7 @@ class AsyncCodeGenerator extends CodeGenerator {
// directly, instead of via throw/catch. Would that be faster?
exceptionHandlers.forEachFinalizer(
(finalizer, _last) => finalizer.setContinuationRethrow(
() => _getVariable(catch_.exception!),
() => _getVariableBoxed(catch_.exception!),
() => _getVariable(catch_.stackTrace!),
));
b.throw_(translator.exceptionTag);
@ -1304,7 +1304,7 @@ class AsyncCodeGenerator extends CodeGenerator {
exceptionHandlers.forEachFinalizer((finalizer, _last) {
finalizer.setContinuationRethrow(
() => _getVariable(catchVars.exception),
() => _getVariableBoxed(catchVars.exception),
() => _getVariable(catchVars.stackTrace),
);
});
@ -1413,24 +1413,33 @@ class AsyncCodeGenerator extends CodeGenerator {
}
}
void _getVariable(VariableDeclaration variable) {
w.ValueType _getVariable(VariableDeclaration variable) {
final w.Local? local = locals[variable];
final Capture? capture = closures.captures[variable];
if (capture != null) {
if (!capture.written && local != null) {
b.local_get(local);
return local.type;
} else {
b.local_get(capture.context.currentLocal);
b.struct_get(capture.context.struct, capture.fieldIndex);
return capture.context.struct.fields[capture.fieldIndex].type.unpacked;
}
} else {
if (local == null) {
throw "Write of undefined variable ${variable}";
}
b.local_get(local);
return local.type;
}
}
/// Same as [_getVariable], but boxes the value if it's not already boxed.
void _getVariableBoxed(VariableDeclaration variable) {
final varType = _getVariable(variable);
translator.convertType(function, varType, translator.topInfo.nullableType);
}
void _getCurrentException() {
b.local_get(suspendStateLocal);
b.struct_get(asyncSuspendStateInfo.struct,

View file

@ -330,21 +330,26 @@ class _WasmTransformer extends Transformer {
// Future<void> Function() #body = () async {
// Completer<bool> #completer = Completer<bool>();
// #controller.add(#completer);
// await #completer.future;
// ...
// #controller.add(i);
// #completer = Completer<bool>();
// #controller.add(#completer)
// await #completer.future;
// ...
// await for (var i in bar) {
// try {
// await #completer.future;
// ...
// #controller.add(i);
// #completer = Completer<bool>();
// #controller.add(#completer)
// await #completer.future;
// ...
// await for (var i in bar) {
// #controller.add(i);
// #completer = Completer<bool>();
// #controller.add(#completer)
// await #completer.future;
// }
// ...
// } catch (e) {
// #controller.addError(e);
// } finally {
// #controller.close();
// }
// ...
// #controller.close();
// };
// bool isEven = false;
// bool isFirst = true;
@ -411,15 +416,45 @@ class _WasmTransformer extends Transformer {
functionNode.body?.accept<TreeNode>(this) as Statement?;
_asyncStarFrames.removeLast();
// Try-catch-finally around the body to call `controller.addError` and
// `controller.close`.
final exceptionVar = VariableDeclaration(null, isSynthesized: true);
final Procedure controllerAddErrorProc = coreTypes.index
.getProcedure('dart:async', 'StreamController', 'addError');
final FunctionType controllerAddErrorType =
Substitution.fromInterfaceType(controllerNullableObjectType)
.substituteType(controllerAddErrorProc.function
.computeThisFunctionType(Nullability.nonNullable))
as FunctionType;
final tryCatch = TryCatch(
Block([
ExpressionStatement(_awaitCompleterFuture(completer, fileOffset)),
if (transformedBody != null) transformedBody,
]),
[
Catch(
exceptionVar,
ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(controller),
Name('addError'),
Arguments([VariableGet(exceptionVar)]),
interfaceTarget: controllerAddErrorProc,
functionType: controllerAddErrorType,
)),
)
],
);
final tryFinally =
TryFinally(tryCatch, ExpressionStatement(callControllerClose));
// Locally declare body function.
final bodyFunction = FunctionNode(
Block([
completer,
ExpressionStatement(
_addCompleterToController(controller, completer, fileOffset)),
ExpressionStatement(_awaitCompleterFuture(completer, fileOffset)),
if (transformedBody != null) transformedBody,
ExpressionStatement(callControllerClose),
tryFinally,
]),
futureValueType: const VoidType(),
returnType: InterfaceType(

View file

@ -19,61 +19,79 @@ static method asyncMethod(asy::Stream<core::int> stream) → asy::Stream<core::i
dynamic :async_temporary_5;
synthesized asy::Completer<core::bool> #completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_0 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_0 as dynamic;
{
{
#controller.{asy::StreamController::add}(1){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_1 as dynamic;
}
{
#controller.{asy::StreamController::add}(2){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_2 as dynamic;
}
{
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
synthesized core::bool #jumpSentinel = #C1;
{
core::int #t1 = 0;
core::Object #t2;
core::StackTrace #t3;
try {
#L1:
for (; ; ) {
:async_temporary_4 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
if(#jumpSentinel = :async_temporary_4 as dynamic) {
synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
{
#controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_3 as dynamic;
core::int #t1 = 0;
core::Object #t2;
core::StackTrace #t3;
try
try {
:async_temporary_0 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_0 as dynamic;
{
{
#controller.{asy::StreamController::add}(1){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_1 as dynamic;
}
{
#controller.{asy::StreamController::add}(2){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_2 as dynamic;
}
{
synthesized asy::_StreamIterator<core::int> #forIterator = new asy::_StreamIterator::•<core::int>(stream);
synthesized core::bool #jumpSentinel = #C1;
{
core::int #t4 = 0;
core::Object #t5;
core::StackTrace #t6;
try {
#L1:
for (; ; ) {
:async_temporary_4 = await #forIterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>};
if(#jumpSentinel = :async_temporary_4 as dynamic) {
synthesized core::int #awaitForVar = #forIterator.{asy::_StreamIterator::current}{core::int};
{
#controller.{asy::StreamController::add}(#awaitForVar){(core::Object?) → void};
#completer = asy::Completer::•<core::bool>();
#controller.{asy::StreamController::add}(#completer){(core::Object?) → void};
:async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future<core::bool>};
:async_temporary_3 as dynamic;
}
}
else
break #L1;
}
}
finally {
if(#jumpSentinel) {
:async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
:async_temporary_5;
}
#t4;
#t5;
#t6;
}
}
else
break #L1;
}
}
finally {
if(#jumpSentinel) {
:async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
:async_temporary_5;
}
#t1;
#t2;
#t3;
}
}
on dynamic catch(dynamic #t7, core::StackTrace #t8) {
#controller.{asy::StreamController::addError}(#t7){(core::Object, [core::StackTrace?]) → void};
#t7;
#t8;
}
finally {
#controller.{asy::StreamController::close}(){() → asy::Future<dynamic>};
#t1;
#t2;
#t3;
}
}
#controller.{asy::StreamController::close}(){() → asy::Future<dynamic>};
};
synthesized core::bool #isFirst = #C2;
synthesized core::bool #isEven = #C1;