From 9d1dd8976890cd0047bf2d72ed03437f1a5f5016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Mon, 22 May 2023 20:31:38 +0000 Subject: [PATCH] [dart2wasm] Handle exceptions and returns in async* functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Commit-Queue: Ömer Ağacan --- pkg/dart2wasm/lib/async.dart | 15 ++- pkg/dart2wasm/lib/transformers.dart | 61 ++++++++-- .../yield.dart.strong.transformed.expect | 112 ++++++++++-------- 3 files changed, 125 insertions(+), 63 deletions(-) diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart index 5ae734be1bc..f4be5cf542d 100644 --- a/pkg/dart2wasm/lib/async.dart +++ b/pkg/dart2wasm/lib/async.dart @@ -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, diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart index d77ac5990ba..532987679b6 100644 --- a/pkg/dart2wasm/lib/transformers.dart +++ b/pkg/dart2wasm/lib/transformers.dart @@ -330,21 +330,26 @@ class _WasmTransformer extends Transformer { // Future Function() #body = () async { // Completer #completer = Completer(); // #controller.add(#completer); - // await #completer.future; - // ... - // #controller.add(i); - // #completer = Completer(); - // #controller.add(#completer) - // await #completer.future; - // ... - // await for (var i in bar) { + // try { + // await #completer.future; + // ... // #controller.add(i); // #completer = Completer(); // #controller.add(#completer) // await #completer.future; + // ... + // await for (var i in bar) { + // #controller.add(i); + // #completer = Completer(); + // #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(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( diff --git a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect index d3864507bce..f2c2499d2a8 100644 --- a/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect +++ b/pkg/front_end/testcases/dart2wasm/yield.dart.strong.transformed.expect @@ -19,61 +19,79 @@ static method asyncMethod(asy::Stream stream) → asy::Stream #completer = asy::Completer::•(); #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; - :async_temporary_0 = await #completer.{asy::Completer::future}{asy::Future}; - :async_temporary_0 as dynamic; { - { - #controller.{asy::StreamController::add}(1){(core::Object?) → void}; - #completer = asy::Completer::•(); - #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; - :async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future}; - :async_temporary_1 as dynamic; - } - { - #controller.{asy::StreamController::add}(2){(core::Object?) → void}; - #completer = asy::Completer::•(); - #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; - :async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future}; - :async_temporary_2 as dynamic; - } - { - synthesized asy::_StreamIterator #forIterator = new asy::_StreamIterator::•(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}; - 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::•(); - #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; - :async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future}; - :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}; + :async_temporary_0 as dynamic; + { + { + #controller.{asy::StreamController::add}(1){(core::Object?) → void}; + #completer = asy::Completer::•(); + #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; + :async_temporary_1 = await #completer.{asy::Completer::future}{asy::Future}; + :async_temporary_1 as dynamic; + } + { + #controller.{asy::StreamController::add}(2){(core::Object?) → void}; + #completer = asy::Completer::•(); + #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; + :async_temporary_2 = await #completer.{asy::Completer::future}{asy::Future}; + :async_temporary_2 as dynamic; + } + { + synthesized asy::_StreamIterator #forIterator = new asy::_StreamIterator::•(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}; + 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::•(); + #controller.{asy::StreamController::add}(#completer){(core::Object?) → void}; + :async_temporary_3 = await #completer.{asy::Completer::future}{asy::Future}; + :async_temporary_3 as dynamic; + } + } + else + break #L1; + } + } + finally { + if(#jumpSentinel) { + :async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future}; + :async_temporary_5; + } + #t4; + #t5; + #t6; } } - else - break #L1; } } - finally { - if(#jumpSentinel) { - :async_temporary_5 = await #forIterator.{asy::_StreamIterator::cancel}(){() → asy::Future}; - :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}; + #t1; + #t2; + #t3; } } - #controller.{asy::StreamController::close}(){() → asy::Future}; }; synthesized core::bool #isFirst = #C2; synthesized core::bool #isEven = #C1;