diff --git a/CHANGELOG.md b/CHANGELOG.md index 653c2971fb4..a579da27e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ This allows libraries with no library declarations (and therefore no name) to have parts, and it allows tools to easily find the library of a part file. +* Added support for starting `async` functions synchronously. All tools (VM, + dart2js, DDC) have now a flag `--sync-async` to enable this behavior. + Currently this behavior is opt-in. It will become the default. #### Strong Mode diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart index 7a6f642c402..7a0b6de73fa 100644 --- a/pkg/compiler/lib/src/commandline_options.dart +++ b/pkg/compiler/lib/src/commandline_options.dart @@ -88,6 +88,11 @@ class Flags { // https://gist.github.com/eernstg/4353d7b4f669745bed3a5423e04a453c. static const String genericMethodSyntax = '--generic-method-syntax'; + // Starts `async` functions synchronously. + // + // This is the Dart 2.0 behavior. This flag is only used during the migration. + static const String syncAsync = '--sync-async'; + // Initializing-formal access is enabled by default and cannot be disabled. // For backward compatibility the option is still accepted, but it is ignored. static const String initializingFormalAccess = '--initializing-formal-access'; diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart index 7163d401141..67f69101fbb 100644 --- a/pkg/compiler/lib/src/common_elements.dart +++ b/pkg/compiler/lib/src/common_elements.dart @@ -508,6 +508,8 @@ class CommonElements { FunctionEntity get asyncHelperStart => _findAsyncHelperFunction("_asyncStart"); + FunctionEntity get asyncHelperStartSync => + _findAsyncHelperFunction("_asyncStartSync"); FunctionEntity get asyncHelperAwait => _findAsyncHelperFunction("_asyncAwait"); FunctionEntity get asyncHelperReturn => @@ -550,6 +552,12 @@ class CommonElements { ConstructorEntity get syncCompleterConstructor => _env.lookupConstructor(_findAsyncHelperClass("Completer"), "sync"); + ConstructorEntity get asyncAwaitCompleterConstructor => + _env.lookupConstructor(asyncAwaitCompleter, ""); + + ClassEntity get asyncAwaitCompleter => + _findAsyncHelperClass("_AsyncAwaitCompleter"); + ClassEntity get asyncStarController => _findAsyncHelperClass("_AsyncStarStreamController"); diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart index b080d9108da..bd2e4ce9011 100644 --- a/pkg/compiler/lib/src/dart2js.dart +++ b/pkg/compiler/lib/src/dart2js.dart @@ -355,6 +355,7 @@ Future compile(List argv, new OptionHandler(Flags.allowMockCompilation, passThrough), new OptionHandler(Flags.fastStartup, passThrough), new OptionHandler(Flags.genericMethodSyntax, ignoreOption), + new OptionHandler(Flags.syncAsync, passThrough), new OptionHandler(Flags.initializingFormalAccess, ignoreOption), new OptionHandler('${Flags.minify}|-m', implyCompilation), new OptionHandler(Flags.preserveUris, passThrough), diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart index 147a35ee08a..3209051061f 100644 --- a/pkg/compiler/lib/src/js_backend/backend.dart +++ b/pkg/compiler/lib/src/js_backend/backend.dart @@ -700,7 +700,8 @@ class JavaScriptBackend { NativeBasicData nativeBasicData = compiler.frontendStrategy.nativeBasicData; RuntimeTypesNeedBuilder rtiNeedBuilder = compiler.frontendStrategy.createRuntimeTypesNeedBuilder(); - BackendImpacts impacts = new BackendImpacts(commonElements); + BackendImpacts impacts = + new BackendImpacts(compiler.options, commonElements); TypeVariableResolutionAnalysis typeVariableResolutionAnalysis = new TypeVariableResolutionAnalysis( compiler.frontendStrategy.elementEnvironment, @@ -783,7 +784,8 @@ class JavaScriptBackend { CompilerTask task, Compiler compiler, ClosedWorld closedWorld) { ElementEnvironment elementEnvironment = closedWorld.elementEnvironment; CommonElements commonElements = closedWorld.commonElements; - BackendImpacts impacts = new BackendImpacts(commonElements); + BackendImpacts impacts = + new BackendImpacts(compiler.options, commonElements); _typeVariableCodegenAnalysis = new TypeVariableCodegenAnalysis( closedWorld.elementEnvironment, this, commonElements, mirrorsData); _mirrorsCodegenAnalysis = mirrorsResolutionAnalysis.close(); @@ -981,7 +983,8 @@ class JavaScriptBackend { emitter.createEmitter(namer, closedWorld, codegenWorldBuilder, sorter); // TODO(johnniwinther): Share the impact object created in // createCodegenEnqueuer. - BackendImpacts impacts = new BackendImpacts(closedWorld.commonElements); + BackendImpacts impacts = + new BackendImpacts(compiler.options, closedWorld.commonElements); if (compiler.options.disableRtiOptimization) { _rtiSubstitutions = new TrivialRuntimeTypesSubstitutions( closedWorld.elementEnvironment, closedWorld.dartTypes); @@ -1167,13 +1170,20 @@ class JavaScriptBackend { jsAst.Expression code, SourceInformation bodySourceInformation, SourceInformation exitSourceInformation) { + bool startAsyncSynchronously = compiler.options.startAsyncSynchronously; + AsyncRewriterBase rewriter = null; jsAst.Name name = namer.methodPropertyName(element); switch (element.asyncMarker) { case AsyncMarker.ASYNC: + var startFunction = startAsyncSynchronously + ? commonElements.asyncHelperStartSync + : commonElements.asyncHelperStart; + var completerConstructor = startAsyncSynchronously + ? commonElements.asyncAwaitCompleterConstructor + : commonElements.syncCompleterConstructor; rewriter = new AsyncRewriter(reporter, element, - asyncStart: - emitter.staticFunctionAccess(commonElements.asyncHelperStart), + asyncStart: emitter.staticFunctionAccess(startFunction), asyncAwait: emitter.staticFunctionAccess(commonElements.asyncHelperAwait), asyncReturn: @@ -1181,8 +1191,8 @@ class JavaScriptBackend { asyncRethrow: emitter.staticFunctionAccess(commonElements.asyncHelperRethrow), wrapBody: emitter.staticFunctionAccess(commonElements.wrapBody), - completerFactory: emitter - .staticFunctionAccess(commonElements.syncCompleterConstructor), + completerFactory: + emitter.staticFunctionAccess(completerConstructor), safeVariableName: namer.safeVariablePrefixForAsyncRewrite, bodyName: namer.deriveAsyncBodyName(name)); break; diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart index 28360bca8a4..3d0fd4fffb9 100644 --- a/pkg/compiler/lib/src/js_backend/backend_impact.dart +++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart @@ -8,6 +8,7 @@ import '../common/names.dart'; import '../common_elements.dart' show CommonElements, ElementEnvironment; import '../elements/types.dart' show InterfaceType; import '../elements/entities.dart'; +import '../options.dart' show CompilerOptions; import '../universe/selector.dart'; import '../universe/world_impact.dart' show WorldImpact, WorldImpactBuilder, WorldImpactBuilderImpl; @@ -88,9 +89,10 @@ class BackendImpact { /// The JavaScript backend dependencies for various features. class BackendImpacts { + final CompilerOptions _options; final CommonElements _commonElements; - BackendImpacts(this._commonElements); + BackendImpacts(this._options, this._commonElements); BackendImpact _getRuntimeTypeArgument; @@ -126,15 +128,24 @@ class BackendImpacts { BackendImpact _asyncBody; BackendImpact get asyncBody { - return _asyncBody ??= new BackendImpact(staticUses: [ - _commonElements.asyncHelperStart, + var staticUses = [ _commonElements.asyncHelperAwait, _commonElements.asyncHelperReturn, _commonElements.asyncHelperRethrow, - _commonElements.syncCompleterConstructor, _commonElements.streamIteratorConstructor, _commonElements.wrapBody - ]); + ]; + var instantiantedClasses = []; + if (_options.startAsyncSynchronously) { + staticUses.add(_commonElements.asyncAwaitCompleterConstructor); + staticUses.add(_commonElements.asyncHelperStartSync); + instantiantedClasses.add(_commonElements.asyncAwaitCompleter); + } else { + staticUses.add(_commonElements.syncCompleterConstructor); + staticUses.add(_commonElements.asyncHelperStart); + } + return _asyncBody ??= new BackendImpact( + staticUses: staticUses, instantiatedClasses: instantiantedClasses); } BackendImpact _syncStarBody; diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart index 129af4d1135..db380bc2962 100644 --- a/pkg/compiler/lib/src/options.dart +++ b/pkg/compiler/lib/src/options.dart @@ -276,6 +276,9 @@ class CompilerOptions implements DiagnosticOptions { /// Strip option used by dart2dart. final List strips; + /// Whether to start `async` functions synchronously. + final bool startAsyncSynchronously; + /// Create an options object by parsing flags from [options]. factory CompilerOptions.parse( {Uri entryPoint, @@ -356,6 +359,7 @@ class CompilerOptions implements DiagnosticOptions { useMultiSourceInfo: _hasOption(options, Flags.useMultiSourceInfo), useNewSourceInfo: _hasOption(options, Flags.useNewSourceInfo), useStartupEmitter: _hasOption(options, Flags.fastStartup), + startAsyncSynchronously: _hasOption(options, Flags.syncAsync), verbose: _hasOption(options, Flags.verbose)); } @@ -421,6 +425,7 @@ class CompilerOptions implements DiagnosticOptions { bool useMultiSourceInfo: false, bool useNewSourceInfo: false, bool useStartupEmitter: false, + bool startAsyncSynchronously: false, bool verbose: false}) { // TODO(sigmund): should entrypoint be here? should we validate it is not // null? In unittests we use the same compiler to analyze or build multiple @@ -504,6 +509,7 @@ class CompilerOptions implements DiagnosticOptions { useMultiSourceInfo: useMultiSourceInfo, useNewSourceInfo: useNewSourceInfo, useStartupEmitter: useStartupEmitter, + startAsyncSynchronously: startAsyncSynchronously, verbose: verbose); } @@ -559,6 +565,7 @@ class CompilerOptions implements DiagnosticOptions { this.useMultiSourceInfo: false, this.useNewSourceInfo: false, this.useStartupEmitter: false, + this.startAsyncSynchronously: false, this.verbose: false}) : _shownPackageWarnings = shownPackageWarnings; diff --git a/pkg/dev_compiler/tool/ddc b/pkg/dev_compiler/tool/ddc index cc8f4106f71..9846cd68481 100755 --- a/pkg/dev_compiler/tool/ddc +++ b/pkg/dev_compiler/tool/ddc @@ -60,6 +60,12 @@ if [ "$1" = "-k" ]; then shift fi +SYNC_ASYNC=false +if [ "$1" = "--sync-async" ]; then + SYNC_ASYNC=true + shift +fi + BASENAME=$( basename "${1%.*}") LIBROOT=$(cd $( dirname "${1%.*}") && pwd) @@ -104,6 +110,7 @@ echo " let sdk = require(\"dart_sdk\"); let main = require(\"./$BASENAME\").$BASENAME.main; sdk.dart.ignoreWhitelistedErrors(false); + if ($SYNC_ASYNC) sdk.dart.setStartAsyncSynchronously(); try { sdk._isolate_helper.startRootIsolate(main, []); } catch(e) { diff --git a/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart index d773cda0f37..a4889fd1b45 100644 --- a/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart +++ b/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart @@ -17,6 +17,8 @@ import 'dart:_isolate_helper' import 'dart:_foreign_helper' show JS, JSExportName; +import 'dart:_runtime' as dart; + typedef void _Callback(); typedef void _TakeCallback(_Callback callback); @@ -67,7 +69,7 @@ async_(Function() initGenerator) { onError = zone.registerUnaryCallback(onError); } var asyncFuture = new _Future(); - scheduleMicrotask(() { + var body = () { try { iter = JS('', '#[Symbol.iterator]()', initGenerator()); var iteratorValue = JS('', '#.next(null)', iter); @@ -97,9 +99,20 @@ async_(Function() initGenerator) { _Future._chainCoreFuture(onAwait(value), asyncFuture); } } catch (e, s) { - _completeWithErrorCallback(asyncFuture, e, s); + if (dart.startAsyncSynchronously) { + scheduleMicrotask(() { + _completeWithErrorCallback(asyncFuture, e, s); + }); + } else { + _completeWithErrorCallback(asyncFuture, e, s); + } } - }); + }; + if (dart.startAsyncSynchronously) { + body(); + } else { + scheduleMicrotask(body); + } return asyncFuture; } diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/generators.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/generators.dart index dc2d82e5348..d8f20d7c623 100644 --- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/generators.dart +++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/generators.dart @@ -11,6 +11,12 @@ /// stepping stone for proposed ES7 async/await, and uses ES6 Promises. part of dart._runtime; +// TODO(vsm): Remove once this flag is the default. +bool startAsyncSynchronously = false; +void setStartAsyncSynchronously() { + startAsyncSynchronously = true; +} + final _jsIterator = JS('', 'Symbol("_jsIterator")'); final _current = JS('', 'Symbol("_current")'); diff --git a/pkg/front_end/tool/_fasta/command_line.dart b/pkg/front_end/tool/_fasta/command_line.dart index aeb2bb56c65..a25d6bed0b8 100644 --- a/pkg/front_end/tool/_fasta/command_line.dart +++ b/pkg/front_end/tool/_fasta/command_line.dart @@ -228,9 +228,13 @@ ProcessedOptions analyzeCommandLine( final bool strongMode = options.containsKey("--strong-mode") || options.containsKey("--strong"); + final bool syncAsync = options.containsKey("--sync-async"); + final String targetName = options["-t"] ?? options["--target"] ?? "vm"; - final TargetFlags flags = new TargetFlags(strongMode: strongMode); + final TargetFlags flags = + new TargetFlags(strongMode: strongMode, syncAsync: syncAsync); + final Target target = getTarget(targetName, flags); if (target == null) { return throw new CommandLineProblem.deprecated( diff --git a/pkg/kernel/bin/transform.dart b/pkg/kernel/bin/transform.dart index 399de1c174e..aabac5ef8a8 100755 --- a/pkg/kernel/bin/transform.dart +++ b/pkg/kernel/bin/transform.dart @@ -44,7 +44,10 @@ ArgParser parser = new ArgParser() ..addOption('transformation', abbr: 't', help: 'The transformation to apply.', - defaultsTo: 'continuation'); + defaultsTo: 'continuation') + ..addFlag('sync-async', + help: 'Whether `async` functions start synchronously.', + defaultsTo: false); main(List arguments) async { if (arguments.isNotEmpty && arguments[0] == '--batch') { @@ -69,6 +72,7 @@ Future runTransformation(List arguments) async { var output = options['out']; var format = options['format']; var verbose = options['verbose']; + var syncAsync = options['sync-async']; if (output == null) { output = '${input.substring(0, input.lastIndexOf('.'))}.transformed.dill'; @@ -85,7 +89,7 @@ Future runTransformation(List arguments) async { final hierarchy = new ClassHierarchy(program); switch (options['transformation']) { case 'continuation': - program = cont.transformProgram(coreTypes, program); + program = cont.transformProgram(coreTypes, program, syncAsync); break; case 'resolve-mixins': mix.transformLibraries( diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart index f23ccbf78a9..fe1cd4b95e8 100644 --- a/pkg/kernel/lib/core_types.dart +++ b/pkg/kernel/lib/core_types.dart @@ -79,7 +79,9 @@ class CoreTypes { Class _stackTraceClass; Class _streamClass; Class _completerClass; + Class _asyncAwaitCompleterClass; Class _futureOrClass; + Constructor _asyncAwaitCompleterConstructor; Procedure _completerSyncConstructor; Procedure _completerComplete; Procedure _completerCompleteError; @@ -162,11 +164,21 @@ class CoreTypes { return _completerClass ??= _index.getClass('dart:async', 'Completer'); } + Class get asyncAwaitCompleterClass { + return _asyncAwaitCompleterClass ??= + _index.getClass('dart:async', '_AsyncAwaitCompleter'); + } + Procedure get completerSyncConstructor { return _completerSyncConstructor ??= _index.getMember('dart:async', 'Completer', 'sync'); } + Constructor get asyncAwaitCompleterConstructor { + return _asyncAwaitCompleterConstructor ??= + _index.getMember('dart:async', '_AsyncAwaitCompleter', ''); + } + Procedure get completerComplete { return _completerComplete ??= _index.getMember('dart:async', 'Completer', 'complete'); diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart index 342757e2456..1d1e8c9a4df 100644 --- a/pkg/kernel/lib/target/targets.dart +++ b/pkg/kernel/lib/target/targets.dart @@ -15,16 +15,20 @@ import 'vmreify.dart' show VmGenericTypesReifiedTarget; final List targetNames = targets.keys.toList(); class TargetFlags { - bool strongMode; - bool treeShake; - List programRoots; - Uri kernelRuntime; + final bool strongMode; + final bool treeShake; + + /// Whether `async` functions start synchronously. + final bool syncAsync; + final List programRoots; + final Uri kernelRuntime; TargetFlags( {this.strongMode: false, this.treeShake: false, + this.syncAsync: false, this.programRoots: const [], - this.kernelRuntime}) {} + this.kernelRuntime}); } typedef Target _TargetBuilder(TargetFlags flags); diff --git a/pkg/kernel/lib/target/vm.dart b/pkg/kernel/lib/target/vm.dart index a30abbb6e07..5079c6520db 100644 --- a/pkg/kernel/lib/target/vm.dart +++ b/pkg/kernel/lib/target/vm.dart @@ -64,7 +64,7 @@ class VmTarget extends Target { logger?.call("Transformed mixin applications"); // TODO(kmillikin): Make this run on a per-method basis. - transformAsync.transformLibraries(coreTypes, libraries); + transformAsync.transformLibraries(coreTypes, libraries, flags.syncAsync); logger?.call("Transformed async methods"); } diff --git a/pkg/kernel/lib/target/vmcc.dart b/pkg/kernel/lib/target/vmcc.dart index 28522cf79cc..3496882bb08 100644 --- a/pkg/kernel/lib/target/vmcc.dart +++ b/pkg/kernel/lib/target/vmcc.dart @@ -44,7 +44,7 @@ class VmClosureConvertedTarget extends vm_target.VmTarget { performTreeShaking(coreTypes, program); } - cont.transformProgram(coreTypes, program); + cont.transformProgram(coreTypes, program, flags.syncAsync); new SanitizeForVM().transform(program); diff --git a/pkg/kernel/lib/transformations/async.dart b/pkg/kernel/lib/transformations/async.dart index 8539f2d548e..8f8133e25cd 100644 --- a/pkg/kernel/lib/transformations/async.dart +++ b/pkg/kernel/lib/transformations/async.dart @@ -484,8 +484,8 @@ class ExpressionLifter extends Transformer { } visitFunctionNode(FunctionNode node) { - var nestedRewriter = - new RecursiveContinuationRewriter(continuationRewriter.helper); + var nestedRewriter = new RecursiveContinuationRewriter( + continuationRewriter.helper, continuationRewriter.syncAsync); return node.accept(nestedRewriter); } } diff --git a/pkg/kernel/lib/transformations/continuation.dart b/pkg/kernel/lib/transformations/continuation.dart index 2b77554415e..9d8e50e6b74 100644 --- a/pkg/kernel/lib/transformations/continuation.dart +++ b/pkg/kernel/lib/transformations/continuation.dart @@ -12,29 +12,34 @@ import '../visitor.dart'; import 'async.dart'; -void transformLibraries(CoreTypes coreTypes, List libraries) { +void transformLibraries( + CoreTypes coreTypes, List libraries, bool syncAsync) { var helper = new HelperNodes.fromCoreTypes(coreTypes); - var rewriter = new RecursiveContinuationRewriter(helper); + var rewriter = new RecursiveContinuationRewriter(helper, syncAsync); for (var library in libraries) { rewriter.rewriteLibrary(library); } } -Program transformProgram(CoreTypes coreTypes, Program program) { +Program transformProgram(CoreTypes coreTypes, Program program, bool syncAsync) { var helper = new HelperNodes.fromCoreTypes(coreTypes); - var rewriter = new RecursiveContinuationRewriter(helper); + var rewriter = new RecursiveContinuationRewriter(helper, syncAsync); return rewriter.rewriteProgram(program); } class RecursiveContinuationRewriter extends Transformer { final HelperNodes helper; + + /// Whether `async` functions should start synchronously. + final bool syncAsync; + final VariableDeclaration asyncJumpVariable = new VariableDeclaration( ":await_jump_var", initializer: new IntLiteral(0)); final VariableDeclaration asyncContextVariable = new VariableDeclaration(":await_ctx_var"); - RecursiveContinuationRewriter(this.helper); + RecursiveContinuationRewriter(this.helper, this.syncAsync); Program rewriteProgram(Program node) { return node.accept(this); @@ -52,14 +57,15 @@ class RecursiveContinuationRewriter extends Transformer { switch (node.asyncMarker) { case AsyncMarker.Sync: case AsyncMarker.SyncYielding: - node.transformChildren(new RecursiveContinuationRewriter(helper)); + node.transformChildren( + new RecursiveContinuationRewriter(helper, syncAsync)); return node; case AsyncMarker.SyncStar: - return new SyncStarFunctionRewriter(helper, node).rewrite(); + return new SyncStarFunctionRewriter(helper, node, syncAsync).rewrite(); case AsyncMarker.Async: - return new AsyncFunctionRewriter(helper, node).rewrite(); + return new AsyncFunctionRewriter(helper, node, syncAsync).rewrite(); case AsyncMarker.AsyncStar: - return new AsyncStarFunctionRewriter(helper, node).rewrite(); + return new AsyncStarFunctionRewriter(helper, node, syncAsync).rewrite(); } } } @@ -72,8 +78,9 @@ abstract class ContinuationRewriterBase extends RecursiveContinuationRewriter { int capturedTryDepth = 0; // Deepest yield point within a try-block. int capturedCatchDepth = 0; // Deepest yield point within a catch-block. - ContinuationRewriterBase(HelperNodes helper, this.enclosingFunction) - : super(helper); + ContinuationRewriterBase( + HelperNodes helper, this.enclosingFunction, bool syncAsync) + : super(helper, syncAsync); /// Given a container [type], which is an instantiation of the given /// [containerClass] extract its element type. @@ -157,13 +164,14 @@ abstract class ContinuationRewriterBase extends RecursiveContinuationRewriter { class SyncStarFunctionRewriter extends ContinuationRewriterBase { final VariableDeclaration iteratorVariable; - SyncStarFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction) + SyncStarFunctionRewriter( + HelperNodes helper, FunctionNode enclosingFunction, syncAsync) : iteratorVariable = new VariableDeclaration(':iterator') ..type = new InterfaceType(helper.syncIteratorClass, [ ContinuationRewriterBase.elementTypeFrom( helper.iterableClass, enclosingFunction.returnType) ]), - super(helper, enclosingFunction); + super(helper, enclosingFunction, syncAsync); FunctionNode rewrite() { // :sync_op(:iterator) { @@ -256,8 +264,9 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase { ExpressionLifter expressionRewriter; - AsyncRewriterBase(helper, enclosingFunction) - : super(helper, enclosingFunction) {} + AsyncRewriterBase( + HelperNodes helper, FunctionNode enclosingFunction, bool syncAsync) + : super(helper, enclosingFunction, syncAsync) {} void setupAsyncContinuations(List statements) { expressionRewriter = new ExpressionLifter(this); @@ -754,8 +763,9 @@ abstract class AsyncRewriterBase extends ContinuationRewriterBase { class AsyncStarFunctionRewriter extends AsyncRewriterBase { VariableDeclaration controllerVariable; - AsyncStarFunctionRewriter(helper, enclosingFunction) - : super(helper, enclosingFunction); + AsyncStarFunctionRewriter( + HelperNodes helper, FunctionNode enclosingFunction, bool syncAsync) + : super(helper, enclosingFunction, syncAsync); FunctionNode rewrite() { var statements = []; @@ -862,8 +872,9 @@ class AsyncFunctionRewriter extends AsyncRewriterBase { VariableDeclaration completerVariable; VariableDeclaration returnVariable; - AsyncFunctionRewriter(helper, enclosingFunction) - : super(helper, enclosingFunction); + AsyncFunctionRewriter( + HelperNodes helper, FunctionNode enclosingFunction, bool syncAsync) + : super(helper, enclosingFunction, syncAsync); FunctionNode rewrite() { var statements = []; @@ -877,16 +888,29 @@ class AsyncFunctionRewriter extends AsyncRewriterBase { final DartType returnType = new InterfaceType(helper.futureOrClass, [valueType]); var completerTypeArguments = [valueType]; - var completerType = - new InterfaceType(helper.completerClass, completerTypeArguments); - // final Completer :completer = new Completer.sync(); - completerVariable = new VariableDeclaration(":completer", - initializer: new StaticInvocation(helper.completerConstructor, - new Arguments([], types: completerTypeArguments)) - ..fileOffset = enclosingFunction.body?.fileOffset ?? -1, - isFinal: true, - type: completerType); + if (syncAsync) { + final completerType = new InterfaceType( + helper.asyncAwaitCompleterClass, completerTypeArguments); + // final Completer :completer = new _AsyncAwaitCompleter(); + completerVariable = new VariableDeclaration(":completer", + initializer: new ConstructorInvocation( + helper.asyncAwaitCompleterConstructor, + new Arguments([], types: completerTypeArguments)) + ..fileOffset = enclosingFunction.body?.fileOffset ?? -1, + isFinal: true, + type: completerType); + } else { + final completerType = + new InterfaceType(helper.completerClass, completerTypeArguments); + // final Completer :completer = new Completer.sync(); + completerVariable = new VariableDeclaration(":completer", + initializer: new StaticInvocation(helper.completerConstructor, + new Arguments([], types: completerTypeArguments)) + ..fileOffset = enclosingFunction.body?.fileOffset ?? -1, + isFinal: true, + type: completerType); + } statements.add(completerVariable); returnVariable = new VariableDeclaration(":return_value", type: returnType); @@ -894,14 +918,23 @@ class AsyncFunctionRewriter extends AsyncRewriterBase { setupAsyncContinuations(statements); - // new Future.microtask(:async_op); - var newMicrotaskStatement = new ExpressionStatement(new StaticInvocation( - helper.futureMicrotaskConstructor, - new Arguments([new VariableGet(nestedClosureVariable)], - types: [const DynamicType()])) - ..fileOffset = enclosingFunction.fileOffset); - statements.add(newMicrotaskStatement); - + if (syncAsync) { + // :completer.start(:async_op); + var startStatement = new ExpressionStatement(new MethodInvocation( + new VariableGet(completerVariable), + new Name('start'), + new Arguments([new VariableGet(nestedClosureVariable)])) + ..fileOffset = enclosingFunction.fileOffset); + statements.add(startStatement); + } else { + // new Future.microtask(:async_op); + var newMicrotaskStatement = new ExpressionStatement(new StaticInvocation( + helper.futureMicrotaskConstructor, + new Arguments([new VariableGet(nestedClosureVariable)], + types: [const DynamicType()])) + ..fileOffset = enclosingFunction.fileOffset); + statements.add(newMicrotaskStatement); + } // return :completer.future; var completerGet = new VariableGet(completerVariable); var returnStatement = new ReturnStatement(new PropertyGet(completerGet, @@ -964,9 +997,11 @@ class HelperNodes { final Procedure asyncThenWrapper; final Procedure awaitHelper; final Class completerClass; + final Class asyncAwaitCompleterClass; final Member completerComplete; final Member completerCompleteError; final Member completerConstructor; + final Member asyncAwaitCompleterConstructor; final Member completerFuture; final Library coreLibrary; final CoreTypes coreTypes; @@ -1001,9 +1036,11 @@ class HelperNodes { this.asyncThenWrapper, this.awaitHelper, this.completerClass, + this.asyncAwaitCompleterClass, this.completerComplete, this.completerCompleteError, this.completerConstructor, + this.asyncAwaitCompleterConstructor, this.completerFuture, this.coreLibrary, this.coreTypes, @@ -1039,9 +1076,11 @@ class HelperNodes { coreTypes.asyncThenWrapperHelperProcedure, coreTypes.awaitHelperProcedure, coreTypes.completerClass, + coreTypes.asyncAwaitCompleterClass, coreTypes.completerComplete, coreTypes.completerCompleteError, coreTypes.completerSyncConstructor, + coreTypes.asyncAwaitCompleterConstructor, coreTypes.completerFuture, coreTypes.coreLibrary, coreTypes, diff --git a/pkg/vm/bin/gen_kernel.dart b/pkg/vm/bin/gen_kernel.dart index 8a501f59d75..1a5b13c8625 100644 --- a/pkg/vm/bin/gen_kernel.dart +++ b/pkg/vm/bin/gen_kernel.dart @@ -25,6 +25,7 @@ final ArgParser _argParser = new ArgParser(allowTrailingOptions: true) 'Produce kernel file for AOT compilation (enables global transformations).', defaultsTo: false) ..addFlag('strong-mode', help: 'Enable strong mode', defaultsTo: true) + ..addFlag('sync-async', help: 'Start `async` functions synchronously') ..addFlag('embed-sources', help: 'Embed source files in the generated kernel program', defaultsTo: true); @@ -62,12 +63,14 @@ Future compile(List arguments) async { final String packages = options['packages']; final bool strongMode = options['strong-mode']; final bool aot = options['aot']; + final bool syncAsync = options['sync-async']; ErrorDetector errorDetector = new ErrorDetector(); final CompilerOptions compilerOptions = new CompilerOptions() ..strongMode = strongMode - ..target = new VmTarget(new TargetFlags(strongMode: strongMode)) + ..target = new VmTarget( + new TargetFlags(strongMode: strongMode, syncAsync: syncAsync)) ..linkedDependencies = [Uri.base.resolve(platformKernel)] ..packagesFileUri = packages != null ? Uri.base.resolve(packages) : null ..reportMessages = true diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart index fef4a4d28f5..60e73f2d579 100644 --- a/pkg/vm/bin/kernel_service.dart +++ b/pkg/vm/bin/kernel_service.dart @@ -49,7 +49,9 @@ abstract class Compiler { CompilerOptions options; Compiler(this.fileSystem, Uri platformKernelPath, - {this.strongMode: false, bool suppressWarnings: false}) { + {this.strongMode: false, + bool suppressWarnings: false, + bool syncAsync: false}) { Uri packagesUri = (Platform.packageConfig != null) ? Uri.parse(Platform.packageConfig) : null; @@ -60,12 +62,14 @@ abstract class Compiler { print("DFE: Platform.resolvedExecutable: ${Platform.resolvedExecutable}"); print("DFE: platformKernelPath: ${platformKernelPath}"); print("DFE: strongMode: ${strongMode}"); + print("DFE: syncAsync: ${syncAsync}"); } options = new CompilerOptions() ..strongMode = strongMode ..fileSystem = fileSystem - ..target = new VmTarget(new TargetFlags(strongMode: strongMode)) + ..target = new VmTarget( + new TargetFlags(strongMode: strongMode, syncAsync: syncAsync)) ..packagesFileUri = packagesUri ..sdkSummary = platformKernelPath ..verbose = verbose @@ -100,9 +104,11 @@ class IncrementalCompiler extends Compiler { IncrementalKernelGenerator generator; IncrementalCompiler(FileSystem fileSystem, Uri platformKernelPath, - {bool strongMode: false, bool suppressWarnings: false}) + {bool strongMode: false, bool suppressWarnings: false, syncAsync: false}) : super(fileSystem, platformKernelPath, - strongMode: strongMode, suppressWarnings: suppressWarnings); + strongMode: strongMode, + suppressWarnings: suppressWarnings, + syncAsync: syncAsync); @override Future compileInternal(Uri script) async { @@ -123,9 +129,12 @@ class SingleShotCompiler extends Compiler { SingleShotCompiler(FileSystem fileSystem, Uri platformKernelPath, {this.requireMain: false, bool strongMode: false, - bool suppressWarnings: false}) + bool suppressWarnings: false, + bool syncAsync: false}) : super(fileSystem, platformKernelPath, - strongMode: strongMode, suppressWarnings: suppressWarnings); + strongMode: strongMode, + suppressWarnings: suppressWarnings, + syncAsync: syncAsync); @override Future compileInternal(Uri script) async { @@ -139,7 +148,9 @@ final Map isolateCompilers = new Map(); Future lookupOrBuildNewIncrementalCompiler(int isolateId, List sourceFiles, Uri platformKernelPath, List platformKernel, - {bool strongMode: false, bool suppressWarnings: false}) async { + {bool strongMode: false, + bool suppressWarnings: false, + bool syncAsync: false}) async { IncrementalCompiler compiler; if (isolateCompilers.containsKey(isolateId)) { compiler = isolateCompilers[isolateId]; @@ -163,7 +174,9 @@ Future lookupOrBuildNewIncrementalCompiler(int isolateId, // isolate needs to receive a message indicating that particular // isolate was shut down. Message should be handled here in this script. compiler = new IncrementalCompiler(fileSystem, platformKernelPath, - strongMode: strongMode, suppressWarnings: suppressWarnings); + strongMode: strongMode, + suppressWarnings: suppressWarnings, + syncAsync: syncAsync); isolateCompilers[isolateId] = compiler; } return compiler; @@ -180,6 +193,7 @@ Future _processLoadRequest(request) async { final int isolateId = request[6]; final List sourceFiles = request[7]; final bool suppressWarnings = request[8]; + final bool syncAsync = request[9]; final Uri script = Uri.base.resolve(inputFileUri); Uri platformKernelPath = null; @@ -202,7 +216,7 @@ Future _processLoadRequest(request) async { if (incremental) { compiler = await lookupOrBuildNewIncrementalCompiler( isolateId, sourceFiles, platformKernelPath, platformKernel, - suppressWarnings: suppressWarnings); + suppressWarnings: suppressWarnings, syncAsync: syncAsync); } else { final FileSystem fileSystem = sourceFiles == null && platformKernel == null ? StandardFileSystem.instance @@ -210,7 +224,8 @@ Future _processLoadRequest(request) async { compiler = new SingleShotCompiler(fileSystem, platformKernelPath, requireMain: sourceFiles == null, strongMode: strong, - suppressWarnings: suppressWarnings); + suppressWarnings: suppressWarnings, + syncAsync: syncAsync); } CompilationResult result; @@ -304,6 +319,7 @@ train(String scriptUri, String platformKernelPath) { 1 /* isolateId chosen randomly */, null /* source files */, false /* suppress warnings */, + false /* synchronous async */, ]; _processLoadRequest(request); } diff --git a/pkg/vm/tool/precompiler2 b/pkg/vm/tool/precompiler2 index faf92c07fbb..69062fe5a01 100755 --- a/pkg/vm/tool/precompiler2 +++ b/pkg/vm/tool/precompiler2 @@ -16,6 +16,7 @@ set -e OPTIONS=() PACKAGES= +SYNC_ASYNC= ARGV=() for arg in "$@"; do @@ -23,6 +24,9 @@ for arg in "$@"; do --packages=*) PACKAGES="$arg" ;; + --sync-async) + SYNC_ASYNC="--sync-async" + ;; --*) OPTIONS+=("$arg") ;; @@ -72,6 +76,7 @@ BIN_DIR="$OUT_DIR/$DART_CONFIGURATION" "${SDK_DIR}/pkg/vm/bin/gen_kernel.dart" \ --platform "${BIN_DIR}/vm_platform_strong.dill" \ --aot \ + $SYNC_ASYNC \ $PACKAGES \ -o "$SNAPSHOT_FILE.dill" \ "$SOURCE_FILE" diff --git a/runtime/lib/async_patch.dart b/runtime/lib/async_patch.dart index de47bf20a10..19d5b6a51b5 100644 --- a/runtime/lib/async_patch.dart +++ b/runtime/lib/async_patch.dart @@ -17,6 +17,43 @@ import "dart:_internal" show VMLibraryHooks, patch; // Equivalent of calling FATAL from C++ code. _fatal(msg) native "DartAsync_fatal"; +class _AsyncAwaitCompleter implements Completer { + final _completer = new Completer.sync(); + bool isSync; + + _AsyncAwaitCompleter() : isSync = false; + + void complete([FutureOr value]) { + if (isSync) { + _completer.complete(value); + } else if (value is Future) { + value.then(_completer.complete, onError: _completer.completeError); + } else { + scheduleMicrotask(() { + _completer.complete(value); + }); + } + } + + void completeError(e, [st]) { + if (isSync) { + _completer.completeError(e, st); + } else { + scheduleMicrotask(() { + _completer.completeError(e, st); + }); + } + } + + void start(f) { + f(); + isSync = true; + } + + Future get future => _completer.future; + bool get isCompleted => _completer.isCompleted; +} + // We need to pass the value as first argument and leave the second and third // arguments empty (used for error handling). // See vm/ast_transformer.cc for usage. diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h index 67d23d68126..b8242e4bbd0 100644 --- a/runtime/vm/flag_list.h +++ b/runtime/vm/flag_list.h @@ -157,6 +157,7 @@ C(stress_async_stacks, false, false, bool, false, \ "Stress test async stack traces") \ P(strong, bool, false, "Enable strong mode.") \ + P(sync_async, bool, false, "Start `async` functions synchronously.") \ R(support_ast_printer, false, bool, true, "Support the AST printer.") \ R(support_compiler_stats, false, bool, true, "Support compiler stats.") \ R(support_disassembler, false, bool, true, "Support the disassembler.") \ diff --git a/runtime/vm/kernel_isolate.cc b/runtime/vm/kernel_isolate.cc index 38afdd2d556..c0f89363a87 100644 --- a/runtime/vm/kernel_isolate.cc +++ b/runtime/vm/kernel_isolate.cc @@ -386,6 +386,10 @@ class KernelCompilationRequest : public ValueObject { suppress_warnings.type = Dart_CObject_kBool; suppress_warnings.value.as_bool = FLAG_suppress_fe_warnings; + Dart_CObject dart_sync_async; + dart_sync_async.type = Dart_CObject_kBool; + dart_sync_async.value.as_bool = FLAG_sync_async; + Dart_CObject* message_arr[] = {&tag, &send_port, &uri, @@ -394,7 +398,8 @@ class KernelCompilationRequest : public ValueObject { &dart_strong, &isolate_id, &files, - &suppress_warnings}; + &suppress_warnings, + &dart_sync_async}; message.value.as_array.values = message_arr; message.value.as_array.length = ARRAY_SIZE(message_arr); // Send the message. diff --git a/runtime/vm/parser.cc b/runtime/vm/parser.cc index 6e2e6394c31..a144a86214d 100644 --- a/runtime/vm/parser.cc +++ b/runtime/vm/parser.cc @@ -7574,30 +7574,42 @@ SequenceNode* Parser::CloseAsyncFunction(const Function& closure, Symbols::AsyncStackTraceVar(), false); ASSERT((existing_var != NULL) && existing_var->is_captured()); - // Create and return a new future that executes a closure with the current - // body. + // Create a completer that executes a closure with the current body and + // return the corresponding future. // No need to capture parameters or other variables, since they have already // been captured in the corresponding scope as the body has been parsed within // a nested block (contained in the async function's block). - const Class& future = Class::ZoneHandle(Z, I->object_store()->future_class()); - ASSERT(!future.IsNull()); - const Function& constructor = Function::ZoneHandle( - Z, future.LookupFunction(Symbols::FutureMicrotask())); - ASSERT(!constructor.IsNull()); - const Class& completer = - Class::ZoneHandle(Z, I->object_store()->completer_class()); - ASSERT(!completer.IsNull()); - const Function& completer_constructor = Function::ZoneHandle( - Z, completer.LookupFunction(Symbols::CompleterSyncConstructor())); - ASSERT(!completer_constructor.IsNull()); + + const Library& async_lib = Library::Handle(Z, Library::AsyncLibrary()); LocalVariable* async_completer = current_block_->scope->LookupVariable(Symbols::AsyncCompleter(), false); const TokenPosition token_pos = ST(closure_body->token_pos()); - // Add to AST: - // :async_completer = new Completer.sync(); + + Function& completer_constructor = Function::ZoneHandle(Z); + if (FLAG_sync_async) { + const Class& completer_class = Class::Handle( + Z, async_lib.LookupClassAllowPrivate(Symbols::_AsyncAwaitCompleter())); + ASSERT(!completer_class.IsNull()); + completer_constructor = completer_class.LookupConstructorAllowPrivate( + Symbols::_AsyncAwaitCompleterConstructor()); + ASSERT(!completer_constructor.IsNull()); + + // Add to AST: + // :async_completer = new _AsyncAwaitCompleter(); + } else { + const Class& completer = + Class::Handle(Z, I->object_store()->completer_class()); + ASSERT(!completer.IsNull()); + completer_constructor = + completer.LookupFunction(Symbols::CompleterSyncConstructor()); + ASSERT(!completer_constructor.IsNull()); + + // Add to AST: + // :async_completer = new Completer.sync(); + } ArgumentListNode* empty_args = new (Z) ArgumentListNode(token_pos); ConstructorCallNode* completer_constructor_node = new (Z) ConstructorCallNode(token_pos, TypeArguments::ZoneHandle(Z), @@ -7624,8 +7636,6 @@ SequenceNode* Parser::CloseAsyncFunction(const Function& closure, new (Z) StoreLocalNode(token_pos, async_op_var, cn); current_block_->statements->Add(store_async_op); - const Library& async_lib = Library::Handle(Library::AsyncLibrary()); - if (FLAG_causal_async_stacks) { // Add to AST: // :async_stack_trace = _asyncStackTraceHelper(); @@ -7689,13 +7699,29 @@ SequenceNode* Parser::CloseAsyncFunction(const Function& closure, current_block_->statements->Add(store_async_catch_error_callback); - // Add to AST: - // new Future.microtask(:async_op); - ArgumentListNode* arguments = new (Z) ArgumentListNode(token_pos); - arguments->Add(new (Z) LoadLocalNode(token_pos, async_op_var)); - ConstructorCallNode* future_node = new (Z) ConstructorCallNode( - token_pos, TypeArguments::ZoneHandle(Z), constructor, arguments); - current_block_->statements->Add(future_node); + if (FLAG_sync_async) { + // Add to AST: + // :async_completer.start(:async_op); + ArgumentListNode* arguments = new (Z) ArgumentListNode(token_pos); + arguments->Add(new (Z) LoadLocalNode(token_pos, async_op_var)); + InstanceCallNode* start_call = new (Z) InstanceCallNode( + token_pos, new (Z) LoadLocalNode(token_pos, async_completer), + Symbols::_AsyncAwaitStart(), arguments); + current_block_->statements->Add(start_call); + } else { + // Add to AST: + // new Future.microtask(:async_op); + const Class& future = Class::Handle(Z, I->object_store()->future_class()); + ASSERT(!future.IsNull()); + const Function& constructor = Function::ZoneHandle( + Z, future.LookupFunction(Symbols::FutureMicrotask())); + ASSERT(!constructor.IsNull()); + ArgumentListNode* arguments = new (Z) ArgumentListNode(token_pos); + arguments->Add(new (Z) LoadLocalNode(token_pos, async_op_var)); + ConstructorCallNode* future_node = new (Z) ConstructorCallNode( + token_pos, TypeArguments::ZoneHandle(Z), constructor, arguments); + current_block_->statements->Add(future_node); + } // Add to AST: // return :async_completer.future; diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 0d2dfcd79ed..276657a005c 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -145,6 +145,9 @@ class ObjectPointerVisitor; V(CompleterComplete, "complete") \ V(CompleterCompleteError, "completeError") \ V(CompleterSyncConstructor, "Completer.sync") \ + V(_AsyncAwaitCompleter, "_AsyncAwaitCompleter") \ + V(_AsyncAwaitCompleterConstructor, "_AsyncAwaitCompleter.") \ + V(_AsyncAwaitStart, "start") \ V(CompleterFuture, "future") \ V(StreamIterator, "StreamIterator") \ V(StreamIteratorConstructor, "StreamIterator.") \ diff --git a/sdk/lib/_internal/js_runtime/lib/async_patch.dart b/sdk/lib/_internal/js_runtime/lib/async_patch.dart index be475bd7f36..bec28e99632 100644 --- a/sdk/lib/_internal/js_runtime/lib/async_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/async_patch.dart @@ -132,6 +132,53 @@ class Timer { } } +class _AsyncAwaitCompleter implements Completer { + final _completer = new Completer.sync(); + bool isSync; + + _AsyncAwaitCompleter() : isSync = false; + + void complete([FutureOr value]) { + if (isSync) { + _completer.complete(value); + } else if (value is Future) { + value.then(_completer.complete, onError: _completer.completeError); + } else { + scheduleMicrotask(() { + _completer.complete(value); + }); + } + } + + void completeError(e, [st]) { + if (isSync) { + _completer.completeError(e, st); + } else { + scheduleMicrotask(() { + _completer.completeError(e, st); + }); + } + } + + Future get future => _completer.future; + bool get isCompleted => _completer.isCompleted; +} + +/// Initiates the computation of an `async` function and starts the body +/// synchronously. +/// +/// Used as part of the runtime support for the async/await transformation. +/// +/// This function sets up the first call into the transformed [bodyFunction]. +/// Independently, it takes the [completer] and returns the future of the +/// completer for convenience of the transformed code. +dynamic _asyncStartSync( + _WrappedAsyncBody bodyFunction, _AsyncAwaitCompleter completer) { + bodyFunction(async_error_codes.SUCCESS, null); + completer.isSync = true; + return completer.future; +} + /// Initiates the computation of an `async` function. /// /// Used as part of the runtime support for the async/await transformation. diff --git a/tests/language_2/async_await_test.dart b/tests/language_2/async_await_test.dart index 4048692401c..7c19b9c0a89 100644 --- a/tests/language_2/async_await_test.dart +++ b/tests/language_2/async_await_test.dart @@ -19,8 +19,8 @@ main() { return expect42(f()); }); - test("async waits", () { - // Calling an "async" function won't do anything immediately. + test("async starts synchronously", () { + // Calling an "async" function starts immediately. var result = []; f() async { result.add(1); @@ -31,7 +31,7 @@ main() { var future = f(); result.add(0); return future.whenComplete(() { - expect(result, equals([0, 1])); + expect(result, equals([1, 0])); }); }); @@ -184,7 +184,6 @@ main() { return new Future.error("err"); // Not awaited. } - ; return throwsErr(f()); }); diff --git a/tests/language_2/async_call_test.dart b/tests/language_2/async_call_test.dart index a3643f91263..bc107b659e0 100644 --- a/tests/language_2/async_call_test.dart +++ b/tests/language_2/async_call_test.dart @@ -14,6 +14,8 @@ foo() { bar() async { result += "bar"; + await null; + result += "bar2"; } main() { @@ -21,13 +23,13 @@ main() { () async { var f = new Future(foo); var b = bar(); - Expect.equals("", result); + Expect.equals("bar", result); scheduleMicrotask(() => result += "micro"); await b; await f; // Validates that bar is scheduled as a microtask, before foo. - Expect.equals("barmicrofoo", result); + Expect.equals("barbar2microfoo", result); asyncEnd(); }(); } diff --git a/tests/language_2/async_error_timing_test.dart b/tests/language_2/async_error_timing_test.dart new file mode 100644 index 00000000000..4b840c75384 --- /dev/null +++ b/tests/language_2/async_error_timing_test.dart @@ -0,0 +1,264 @@ +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'package:expect/expect.dart'; +import 'package:async_helper/async_helper.dart'; + +class AsyncTracker { + int runningAsyncs = 0; + List expectedEvents; + final List actualEvents = []; + + AsyncTracker() { + asyncStart(); + } + + void start(String event) { + actualEvents.add("start $event"); + runningAsyncs++; + } + + void stop(String event) { + actualEvents.add("stop $event"); + if (--runningAsyncs == 0) { + Expect.listEquals(expectedEvents, actualEvents); + asyncEnd(); + } + } + + void add(e) { + actualEvents.add(e); + } +} + +void test1() { + var tracker = new AsyncTracker(); + + Future foo() async { + tracker.add("error-foo"); + throw "foo"; + } + + tracker.start("micro1"); + scheduleMicrotask(() { + tracker.start("micro2"); + scheduleMicrotask(() { + tracker.stop("micro2"); + }); + tracker.stop("micro1"); + }); + + tracker.start("foo"); + foo().catchError((e) { + tracker.stop("foo"); + }); + tracker.start("micro3"); + scheduleMicrotask(() { + tracker.stop("micro3"); + }); + + tracker.expectedEvents = [ + "start micro1", + "start foo", + "error-foo", + "start micro3", + "start micro2", + "stop micro1", + "stop foo", + "stop micro3", + "stop micro2", + ]; +} + +void test2() { + var tracker = new AsyncTracker(); + + Future bar() async { + tracker.add("await null"); + await null; + tracker.add("error-bar"); + throw "bar"; + } + + tracker.start("micro1"); + scheduleMicrotask(() { + tracker.start("micro2"); + scheduleMicrotask(() { + tracker.start("micro3"); + scheduleMicrotask(() { + tracker.stop("micro3"); + }); + tracker.stop("micro2"); + }); + tracker.stop("micro1"); + }); + + tracker.start("bar"); + bar().catchError((e) { + tracker.stop("bar"); + }); + tracker.start("micro4"); + scheduleMicrotask(() { + tracker.start("micro5"); + scheduleMicrotask(() { + tracker.stop("micro5"); + }); + tracker.stop("micro4"); + }); + + tracker.expectedEvents = [ + "start micro1", + "start bar", + "await null", + "start micro4", + "start micro2", + "stop micro1", + "error-bar", + "stop bar", + "start micro5", + "stop micro4", + "start micro3", + "stop micro2", + "stop micro5", + "stop micro3", + ]; +} + +void test3() { + var tracker = new AsyncTracker(); + + Future gee() async { + tracker.add("error-gee"); + return new Future.error("gee"); + } + + tracker.start("micro1"); + scheduleMicrotask(() { + tracker.start("micro2"); + scheduleMicrotask(() { + tracker.stop("micro2"); + }); + tracker.stop("micro1"); + }); + + tracker.start("gee"); + gee().catchError((e) { + tracker.stop("gee"); + }); + tracker.start("micro3"); + scheduleMicrotask(() { + tracker.stop("micro3"); + }); + + tracker.expectedEvents = [ + "start micro1", + "start gee", + "error-gee", + "start micro3", + "start micro2", + "stop micro1", + "stop gee", + "stop micro3", + "stop micro2", + ]; +} + +void test4() { + var tracker = new AsyncTracker(); + + Future toto() async { + tracker.add("await null"); + await null; + tracker.add("error-toto"); + return new Future.error("toto"); + } + + tracker.start("micro1"); + scheduleMicrotask(() { + tracker.start("micro2"); + scheduleMicrotask(() { + tracker.start("micro3"); + scheduleMicrotask(() { + tracker.stop("micro3"); + }); + tracker.stop("micro2"); + }); + tracker.stop("micro1"); + }); + + tracker.start("toto"); + toto().catchError((e) { + tracker.stop("toto"); + }); + tracker.start("micro4"); + scheduleMicrotask(() { + tracker.start("micro5"); + scheduleMicrotask(() { + tracker.stop("micro5"); + }); + tracker.stop("micro4"); + }); + + tracker.expectedEvents = [ + "start micro1", + "start toto", + "await null", + "start micro4", + "start micro2", + "stop micro1", + "error-toto", + "start micro5", + "stop micro4", + "start micro3", + "stop micro2", + "stop toto", + "stop micro5", + "stop micro3", + ]; +} + +void test5() { + var tracker = new AsyncTracker(); + + Future foo() async { + tracker.add("throw"); + throw "foo"; + } + + bar() async { + tracker.start('micro'); + scheduleMicrotask(() { + tracker.stop('micro'); + }); + try { + tracker.start('foo'); + await foo(); + } catch (e) { + tracker.stop('foo'); + } + tracker.stop("bar"); + } + + tracker.start('bar'); + + tracker.expectedEvents = [ + "start bar", + "start micro", + "start foo", + "throw", + "stop micro", + "stop foo", + "stop bar", + ]; +} + +main() { + asyncStart(); + test1(); + test2(); + test3(); + test4(); + asyncEnd(); +} diff --git a/tests/language_2/asyncstar_throw_in_catch_test.dart b/tests/language_2/asyncstar_throw_in_catch_test.dart index e1a3a9550b7..884b492c57e 100644 --- a/tests/language_2/asyncstar_throw_in_catch_test.dart +++ b/tests/language_2/asyncstar_throw_in_catch_test.dart @@ -40,9 +40,9 @@ foo1(Tracer tracer) async* { yield 3; tracer.trace("f"); } finally { - tracer.trace("f"); + tracer.trace("g"); } - tracer.trace("g"); + tracer.trace("h"); } foo2(Tracer tracer) async* { @@ -116,10 +116,10 @@ runTest(test, expectedTrace, expectedError, shouldCancel) { test() async { // TODO(sigurdm): These tests are too dependent on scheduling, and buffering // behavior. - await runTest(foo1, "abcdYefC", null, true); + await runTest(foo1, "abcYdgC", null, true); await runTest(foo2, "abcX", "Error", false); await runTest(foo3, "abcYX", "Error", false); - await runTest(foo4, "abcdYeYfX", "Error2", false); + await runTest(foo4, "abcYdYefX", "Error2", false); } void main() { diff --git a/tests/language_2/await_nonfuture_test.dart b/tests/language_2/await_nonfuture_test.dart index 739f73aa37f..1aa6855ff6d 100644 --- a/tests/language_2/await_nonfuture_test.dart +++ b/tests/language_2/await_nonfuture_test.dart @@ -5,16 +5,23 @@ // VMOptions=--optimization-counter-threshold=5 import 'package:expect/expect.dart'; +import 'package:async_helper/async_helper.dart'; var X = 0; foo() async { - Expect.equals(X, 10); // foo runs after main returns. - return await 5; + Expect.equals(X, 0); + await 5; + Expect.equals(X, 10); + return await 499; } main() { + asyncStart(); var f = foo(); - f.then((res) => print("f completed with $res")); + f.then((res) { + Expect.equals(499, res); + asyncEnd(); + }); X = 10; } diff --git a/tests/language_2/await_not_started_immediately_test.dart b/tests/language_2/await_started_immediately_test.dart similarity index 84% rename from tests/language_2/await_not_started_immediately_test.dart rename to tests/language_2/await_started_immediately_test.dart index 5101c58b697..c21591f5c0b 100644 --- a/tests/language_2/await_not_started_immediately_test.dart +++ b/tests/language_2/await_started_immediately_test.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -// Test that an async function does not start immediately. +// Test that an async function does start immediately. import "package:expect/expect.dart"; import "package:async_helper/async_helper.dart"; @@ -18,5 +18,5 @@ foo() async { void main() { asyncStart(); foo().then((_) => Expect.equals(2, x)).whenComplete(asyncEnd); - Expect.equals(0, x); + Expect.equals(1, x); } diff --git a/tests/language_2/language_2_dart2js.status b/tests/language_2/language_2_dart2js.status index bc5fcd722ab..28a733dc290 100644 --- a/tests/language_2/language_2_dart2js.status +++ b/tests/language_2/language_2_dart2js.status @@ -49,6 +49,7 @@ field_type_check2_test/01: MissingRuntimeError round_test: Pass, Fail, OK # Fixed in ff 35. Common JavaScript engine Math.round bug. [ $compiler == dart2js && $runtime == jsshell ] +async_call_test: RuntimeError # Timer interface not supported: Issue 7728. async_star_test/01: RuntimeError async_star_test/02: RuntimeError async_star_test/03: RuntimeError @@ -866,6 +867,7 @@ type_check_const_function_typedef2_test: MissingCompileTimeError function_subtype_inline2_test: RuntimeError [ $compiler == dart2js && $dart2js_with_kernel ] +async_error_timing_test: Crash bug31436_test: RuntimeError built_in_identifier_type_annotation_test/22: Crash # Issue 28815 built_in_identifier_type_annotation_test/52: MissingCompileTimeError # Issue 28815 diff --git a/tests/lib_2/async/async_await_sync_completer_test.dart b/tests/lib_2/async/async_await_sync_completer_test.dart index b6887d6b2bf..889632f04ba 100644 --- a/tests/lib_2/async/async_await_sync_completer_test.dart +++ b/tests/lib_2/async/async_await_sync_completer_test.dart @@ -13,6 +13,10 @@ var delayedValue = new Completer(); var delayedError = new Completer(); foo() async { + // Because of this `await null` the function returns and lets the caller + // install handlers. When the function finishes, it can then synchronously + // propagate the values. + await null; new Future.microtask(() => 'in microtask') .then(events.add) .then(delayedValue.complete); @@ -20,6 +24,10 @@ foo() async { } bar() async { + // Because of this `await null` the function returns and lets the caller + // install handlers. When the function finishes, it can then synchronously + // propagate the values. + await null; new Future.microtask(() => throw 'in microtask error') .catchError(events.add) .then(delayedError.complete); diff --git a/tests/lib_2/lib_2_dart2js.status b/tests/lib_2/lib_2_dart2js.status index 6cd6ce01b44..07494f7c839 100644 --- a/tests/lib_2/lib_2_dart2js.status +++ b/tests/lib_2/lib_2_dart2js.status @@ -278,6 +278,7 @@ html/interactive_geolocation_test: Skip # Requires allowing geo location. html/custom/created_callback_test: RuntimeError html/fontface_loaded_test: Fail # Support for promises. html/js_typed_interop_lazy_test/01: RuntimeError +html/notification_permission_test: Timeout # Issue 32002 html/private_extension_member_test: RuntimeError isolate/isolate_stress_test: Pass, Slow # Issue 10697 js/null_test: RuntimeError # Issue 30652 diff --git a/tests/lib_2/lib_2_dartdevc.status b/tests/lib_2/lib_2_dartdevc.status index 5881198e03c..dd36b163f5f 100644 --- a/tests/lib_2/lib_2_dartdevc.status +++ b/tests/lib_2/lib_2_dartdevc.status @@ -104,6 +104,7 @@ html/js_util_test: RuntimeError # Issue 29922 html/media_stream_test: RuntimeError # Issue 29922 html/mediasource_test: RuntimeError # Issue 29922 html/no_linked_scripts_htmltest: Skip # Issue 29919 +html/notification_permission_test: Timeout # Issue 32002 html/scripts_htmltest: Skip # Issue 29919 html/transferables_test: CompileTimeError # Issue 30975 html/transition_event_test: Pass, RuntimeError, Timeout # Issue 29922, this test seems flaky diff --git a/tools/testing/dart/browser_test.dart b/tools/testing/dart/browser_test.dart index 011aa2d396d..19b39a73b45 100644 --- a/tools/testing/dart/browser_test.dart +++ b/tools/testing/dart/browser_test.dart @@ -40,7 +40,8 @@ String dart2jsHtml(String title, String scriptPath) { /// The [testName] is the short name of the test without any subdirectory path /// or extension, like "math_test". The [testJSDir] is the relative path to the /// build directory where the dartdevc-generated JS file is stored. -String dartdevcHtml(String testName, String testJSDir, String buildDir) { +String dartdevcHtml(String testName, String testJSDir, String buildDir, + {bool syncAsync}) { var packagePaths = testPackages .map((package) => ' "$package": "/root_dart/$buildDir/gen/utils/' 'dartdevc/pkg/$package",') @@ -86,7 +87,7 @@ window.ddcSettings = { requirejs(["$testName", "dart_sdk", "async_helper"], function($testName, sdk, async_helper) { sdk.dart.ignoreWhitelistedErrors(false); - + if ($syncAsync) sdk.dart.setStartAsyncSynchronously(); // TODO(rnystrom): This uses DDC's forked version of async_helper. Unfork // these packages when possible. async_helper.async_helper.asyncTestInitialize(function() {}); diff --git a/tools/testing/dart/compiler_configuration.dart b/tools/testing/dart/compiler_configuration.dart index da1d9bee698..3bbb2054ddc 100644 --- a/tools/testing/dart/compiler_configuration.dart +++ b/tools/testing/dart/compiler_configuration.dart @@ -1065,6 +1065,7 @@ abstract class VMKernelCompilerMixin { final args = [ _isAot ? '--aot' : '--no-aot', _isStrong ? '--strong-mode' : '--no-strong-mode', + _isStrong ? '--sync-async' : '--no-sync-async', '--platform=$vmPlatform', '-o', dillFile, diff --git a/tools/testing/dart/test_suite.dart b/tools/testing/dart/test_suite.dart index 62cee3fbd36..2e16e4a75f5 100644 --- a/tools/testing/dart/test_suite.dart +++ b/tools/testing/dart/test_suite.dart @@ -814,6 +814,12 @@ class StandardTestSuite extends TestSuite { var commonArguments = commonArgumentsFromFile(info.filePath, info.optionsFromFile); + // TODO(floitsch): Hack. When running the 2.0 tests always start + // async functions synchronously. + if (suiteName.endsWith("_2")) { + commonArguments.insert(0, "--sync-async"); + } + var vmOptionsList = getVmOptions(info.optionsFromFile); assert(!vmOptionsList.isEmpty); @@ -1026,13 +1032,37 @@ class StandardTestSuite extends TestSuite { } else { var jsDir = new Path(compilationTempDir).relativeTo(Repository.dir).toString(); - content = dartdevcHtml(nameNoExt, jsDir, buildDir); + // Always run with synchronous starts of `async` functions. + // If we want to make this dependent on other parameters or flags, + // this flag could be become conditional. + content = dartdevcHtml(nameNoExt, jsDir, buildDir, syncAsync: true); } } var htmlPath = '$tempDir/test.html'; new File(htmlPath).writeAsStringSync(content); + // TODO(floitsch): Hack. When running the 2.0 tests always start + // async functions synchronously. + if (suiteName.endsWith("_2") && + configuration.compiler == Compiler.dart2js) { + if (optionsFromFile == null) { + optionsFromFile = const { + 'sharedOptions': const ['--sync-async'] + }; + } else { + optionsFromFile = new Map.from(optionsFromFile); + var sharedOptions = optionsFromFile['sharedOptions']; + if (sharedOptions == null) { + sharedOptions = const ['--sync-async']; + } else { + sharedOptions = sharedOptions.toList(); + sharedOptions.insert(0, "--sync-async"); + } + optionsFromFile['sharedOptions'] = sharedOptions; + } + } + // Construct the command(s) that compile all the inputs needed by the // browser test. For running Dart in DRT, this will be noop commands. var commands = [];