From 94c120a6ea5c7ab1f7bab3341cfa2bd03f313cf1 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Mon, 11 Jul 2022 18:12:41 +0000 Subject: [PATCH] [vm] Cleanup old async/async*/sync* implementation from kernel This change removes kernel transformation which was used to desugar async/async*/sync* functions in the old implementation of async/async*/sync*. The useful part of the transformation is retained in pkg/vm/lib/transformations/for_in_lowering.dart. TEST=ci Issue: https://github.com/dart-lang/sdk/issues/48378 Change-Id: Ic70c1fb35162a31bcc22eac3a8f6488b61e945b4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249944 Reviewed-by: Slava Egorov Reviewed-by: Johnni Winther Reviewed-by: Martin Kustermann Commit-Queue: Alexander Markov --- .../lib/src/fasta/source/source_loader.dart | 31 - ...issue38253c.dart.strong.transformed.expect | 69 - pkg/frontend_server/lib/frontend_server.dart | 4 +- pkg/kernel/lib/core_types.dart | 54 - pkg/kernel/lib/target/targets.dart | 8 +- pkg/vm/lib/kernel_front_end.dart | 13 +- pkg/vm/lib/target/vm.dart | 22 +- pkg/vm/lib/transformations/async.dart | 672 ------- pkg/vm/lib/transformations/continuation.dart | 1667 ----------------- .../lib/transformations/for_in_lowering.dart | 222 +++ pkg/vm/lib/transformations/lowering.dart | 43 +- runtime/docs/index.md | 2 +- .../async/syncstar_mixed_iterators_test.dart | 4 +- 13 files changed, 271 insertions(+), 2540 deletions(-) delete mode 100644 pkg/front_end/testcases/general/issue38253c.dart.strong.transformed.expect delete mode 100644 pkg/vm/lib/transformations/async.dart delete mode 100644 pkg/vm/lib/transformations/continuation.dart create mode 100644 pkg/vm/lib/transformations/for_in_lowering.dart diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart index 4f62e702420..c039e086328 100644 --- a/pkg/front_end/lib/src/fasta/source/source_loader.dart +++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart @@ -2730,45 +2730,14 @@ class int extends num {} class num {} -class _SyncIterable {} - -class _SyncIterator { - var _current; - var _yieldEachIterable; -} - class Function {} """; /// A minimal implementation of dart:async that is sufficient to create an /// instance of [CoreTypes] and compile program. const String defaultDartAsyncSource = """ -_asyncErrorWrapperHelper(continuation) {} - void _asyncStarMoveNextHelper(var stream) {} -_asyncThenWrapperHelper(continuation) {} - -_awaitHelper(object, thenCallback, errorCallback) {} - -_completeOnAsyncReturn(_future, value, async_jump_var) {} - -_completeWithNoFutureOnAsyncReturn(_future, value, async_jump_var) {} - -_completeOnAsyncError(_future, e, st, async_jump_var) {} - -class _AsyncStarStreamController { - add(event) {} - - addError(error, stackTrace) {} - - addStream(stream) {} - - close() {} - - get stream => null; -} - abstract class Completer { factory Completer.sync() => null; diff --git a/pkg/front_end/testcases/general/issue38253c.dart.strong.transformed.expect b/pkg/front_end/testcases/general/issue38253c.dart.strong.transformed.expect deleted file mode 100644 index eb54dc9b30d..00000000000 --- a/pkg/front_end/testcases/general/issue38253c.dart.strong.transformed.expect +++ /dev/null @@ -1,69 +0,0 @@ -library; -// -// Problems in library: -// -// pkg/front_end/testcases/general/issue38253c.dart:6:3: Error: 'g' isn't a type. -// g f1() {} -// ^ -// -// pkg/front_end/testcases/general/issue38253c.dart:7:3: Error: 'g' isn't a type. -// g f2() async {} -// ^ -// -import self as self; -import "dart:async" as asy; -import "dart:core" as core; - -static field () →* Null a = () → Null { - function f1() → invalid-type {} - function f2() → invalid-type /* originally async */ { - final asy::_Future* :async_future = new asy::_Future::•(); - core::bool* :is_sync = false; - FutureOr* :return_value; - (dynamic) →* dynamic :async_op_then; - (core::Object*, core::StackTrace*) →* dynamic :async_op_error; - core::int* :await_jump_var = 0; - dynamic :await_ctx_var; - function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding - try { - #L1: - {} - asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync); - return; - } - on dynamic catch(dynamic exception, core::StackTrace* stack_trace) { - asy::_completeOnAsyncError(:async_future, exception, stack_trace, :is_sync); - } - :async_op_then = asy::_asyncThenWrapperHelper(:async_op); - :async_op_error = asy::_asyncErrorWrapperHelper(:async_op); - :async_op.call(); - :is_sync = true; - return :async_future; - } - function f3() → core::int* {} - function f4() → asy::Future* /* originally async */ { - final asy::_Future* :async_future = new asy::_Future::•(); - core::bool* :is_sync = false; - FutureOr* :return_value; - (dynamic) →* dynamic :async_op_then; - (core::Object*, core::StackTrace*) →* dynamic :async_op_error; - core::int* :await_jump_var = 0; - dynamic :await_ctx_var; - function :async_op([dynamic :result, dynamic :exception, dynamic :stack_trace]) → dynamic yielding - try { - #L2: - {} - asy::_completeOnAsyncReturn(:async_future, :return_value, :is_sync); - return; - } - on dynamic catch(dynamic exception, core::StackTrace* stack_trace) { - asy::_completeOnAsyncError(:async_future, exception, stack_trace, :is_sync); - } - :async_op_then = asy::_asyncThenWrapperHelper(:async_op); - :async_op_error = asy::_asyncErrorWrapperHelper(:async_op); - :async_op.call(); - :is_sync = true; - return :async_future; - } -}; -static method main() → dynamic {} diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart index 5d7088cc15c..9065163b17b 100644 --- a/pkg/frontend_server/lib/frontend_server.dart +++ b/pkg/frontend_server/lib/frontend_server.dart @@ -58,8 +58,7 @@ ArgParser argParser = ArgParser(allowTrailingOptions: true) help: 'Whether dart:mirrors is supported. By default dart:mirrors is ' 'supported when --aot and --minimal-kernel are not used.', defaultsTo: null) - ..addFlag('compact-async', - help: 'Enable new compact async/await implementation.', defaultsTo: true) + ..addFlag('compact-async', help: 'Obsolete, ignored.', hide: true) ..addFlag('tfa', help: 'Enable global type flow analysis and related transformations in AOT mode.', @@ -543,7 +542,6 @@ class FrontendCompiler implements CompilerInterface { nullSafety: compilerOptions.nnbdMode == NnbdMode.Strong, supportMirrors: options['support-mirrors'] ?? !(options['aot'] || options['minimal-kernel']), - compactAsync: options['compact-async'], ); if (compilerOptions.target == null) { print('Failed to create front-end target ${options['target']}.'); diff --git a/pkg/kernel/lib/core_types.dart b/pkg/kernel/lib/core_types.dart index 70edb2c1a38..1734950a7ad 100644 --- a/pkg/kernel/lib/core_types.dart +++ b/pkg/kernel/lib/core_types.dart @@ -119,57 +119,15 @@ class CoreTypes { CoreTypes(Component component) : index = new LibraryIndex.coreLibraries(component); - late final Procedure asyncErrorWrapperHelperProcedure = - index.getTopLevelProcedure('dart:async', '_asyncErrorWrapperHelper'); - late final Library asyncLibrary = index.getLibrary('dart:async'); - late final Procedure asyncStarStreamControllerAdd = - index.getProcedure('dart:async', '_AsyncStarStreamController', 'add'); - - late final Procedure asyncStarStreamControllerAddError = index.getProcedure( - 'dart:async', '_AsyncStarStreamController', 'addError'); - - late final Procedure asyncStarStreamControllerAddStream = index.getProcedure( - 'dart:async', '_AsyncStarStreamController', 'addStream'); - - late final Class asyncStarStreamControllerClass = - index.getClass('dart:async', '_AsyncStarStreamController'); - - late final Procedure asyncStarStreamControllerClose = - index.getProcedure('dart:async', '_AsyncStarStreamController', 'close'); - - late final Constructor asyncStarStreamControllerDefaultConstructor = - index.getConstructor('dart:async', '_AsyncStarStreamController', ''); - - late final Member asyncStarStreamControllerStream = - index.getMember('dart:async', '_AsyncStarStreamController', 'get:stream'); - late final Procedure asyncStarMoveNextHelper = index.getTopLevelProcedure('dart:async', '_asyncStarMoveNextHelper'); - late final Procedure asyncThenWrapperHelperProcedure = - index.getTopLevelProcedure('dart:async', '_asyncThenWrapperHelper'); - - late final Procedure awaitHelperProcedure = - index.getTopLevelProcedure('dart:async', '_awaitHelper'); - late final Class boolClass = index.getClass('dart:core', 'bool'); late final Class futureImplClass = index.getClass('dart:async', '_Future'); - late final Constructor futureImplConstructor = - index.getConstructor('dart:async', '_Future', ''); - - late final Procedure completeOnAsyncReturn = - index.getTopLevelProcedure('dart:async', '_completeOnAsyncReturn'); - - late final Procedure completeWithNoFutureOnAsyncReturn = index - .getTopLevelProcedure('dart:async', '_completeWithNoFutureOnAsyncReturn'); - - late final Procedure completeOnAsyncError = - index.getTopLevelProcedure('dart:async', '_completeOnAsyncError'); - late final Library coreLibrary = index.getLibrary('dart:core'); late final Class doubleClass = index.getClass('dart:core', 'double'); @@ -287,18 +245,6 @@ class CoreTypes { late final Class symbolClass = index.getClass('dart:core', 'Symbol'); - late final Constructor syncIterableDefaultConstructor = - index.getConstructor('dart:core', '_SyncIterable', ''); - - late final Class syncIteratorClass = - index.getClass('dart:core', '_SyncIterator'); - - late final Member syncIteratorCurrent = - index.getMember('dart:core', '_SyncIterator', '_current'); - - late final Member syncIteratorYieldEachIterable = - index.getMember('dart:core', '_SyncIterator', '_yieldEachIterable'); - late final Class typeClass = index.getClass('dart:core', 'Type'); late final Constructor fallThroughErrorUrlAndLineConstructor = diff --git a/pkg/kernel/lib/target/targets.dart b/pkg/kernel/lib/target/targets.dart index c46623869a0..477f255cc1c 100644 --- a/pkg/kernel/lib/target/targets.dart +++ b/pkg/kernel/lib/target/targets.dart @@ -16,13 +16,11 @@ class TargetFlags { final bool trackWidgetCreation; final bool enableNullSafety; final bool supportMirrors; - final bool compactAsync; const TargetFlags( {this.trackWidgetCreation = false, this.enableNullSafety = false, - this.supportMirrors = true, - this.compactAsync = true}); + this.supportMirrors = true}); @override bool operator ==(other) { @@ -30,8 +28,7 @@ class TargetFlags { return other is TargetFlags && trackWidgetCreation == other.trackWidgetCreation && enableNullSafety == other.enableNullSafety && - supportMirrors == other.supportMirrors && - compactAsync == other.compactAsync; + supportMirrors == other.supportMirrors; } @override @@ -40,7 +37,6 @@ class TargetFlags { hash = 0x3fffffff & (hash * 31 + (hash ^ trackWidgetCreation.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ enableNullSafety.hashCode)); hash = 0x3fffffff & (hash * 31 + (hash ^ supportMirrors.hashCode)); - hash = 0x3fffffff & (hash * 31 + (hash ^ compactAsync.hashCode)); return hash; } } diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart index 6e70384b020..7ffdc7fc5bb 100644 --- a/pkg/vm/lib/kernel_front_end.dart +++ b/pkg/vm/lib/kernel_front_end.dart @@ -81,8 +81,7 @@ void declareCompilerOptions(ArgParser args) { help: 'Whether dart:mirrors is supported. By default dart:mirrors is ' 'supported when --aot and --minimal-kernel are not used.', defaultsTo: null); - args.addFlag('compact-async', - help: 'Enable new compact async/await implementation.', defaultsTo: true); + args.addFlag('compact-async', help: 'Obsolete, ignored.', hide: true); args.addOption('depfile', help: 'Path to output Ninja depfile'); args.addOption('from-dill', help: 'Read existing dill file instead of compiling from sources', @@ -203,7 +202,6 @@ Future runCompiler(ArgResults options, String usage) async { final String? manifestFilename = options['manifest']; final String? dataDir = options['component-name'] ?? options['data-dir']; final bool? supportMirrors = options['support-mirrors']; - final bool compactAsync = options['compact-async']; final bool minimalKernel = options['minimal-kernel']; final bool treeShakeWriteOnlyFields = options['tree-shake-write-only-fields']; @@ -287,8 +285,7 @@ Future runCompiler(ArgResults options, String usage) async { compilerOptions.target = createFrontEndTarget(targetName, trackWidgetCreation: options['track-widget-creation'], nullSafety: compilerOptions.nnbdMode == NnbdMode.Strong, - supportMirrors: supportMirrors ?? !(aot || minimalKernel), - compactAsync: compactAsync); + supportMirrors: supportMirrors ?? !(aot || minimalKernel)); if (compilerOptions.target == null) { print('Failed to create front-end target $targetName.'); return badUsageExitCode; @@ -617,16 +614,14 @@ Future autoDetectNullSafetyMode( Target? createFrontEndTarget(String targetName, {bool trackWidgetCreation = false, bool nullSafety = false, - bool supportMirrors = true, - bool compactAsync = true}) { + bool supportMirrors = true}) { // Make sure VM-specific targets are available. installAdditionalTargets(); final TargetFlags targetFlags = new TargetFlags( trackWidgetCreation: trackWidgetCreation, enableNullSafety: nullSafety, - supportMirrors: supportMirrors, - compactAsync: compactAsync); + supportMirrors: supportMirrors); return getTarget(targetName, targetFlags); } diff --git a/pkg/vm/lib/target/vm.dart b/pkg/vm/lib/target/vm.dart index b706bd5912d..74c4842225b 100644 --- a/pkg/vm/lib/target/vm.dart +++ b/pkg/vm/lib/target/vm.dart @@ -9,11 +9,8 @@ import 'package:kernel/core_types.dart'; import 'package:kernel/reference_from_index.dart'; import 'package:kernel/target/changed_structure_notifier.dart'; import 'package:kernel/target/targets.dart'; -import 'package:kernel/type_environment.dart'; import '../transformations/call_site_annotator.dart' as callSiteAnnotator; -import '../transformations/continuation.dart' as transformAsync - show transformLibraries, transformProcedure; import '../transformations/lowering.dart' as lowering show transformLibraries, transformProcedure; import '../transformations/mixin_full_resolution.dart' as transformMixins @@ -183,15 +180,9 @@ class VmTarget extends Target { logger?.call("Transformed ffi annotations"); } - // TODO(kmillikin): Make this run on a per-method basis. bool productMode = environmentDefines!["dart.vm.product"] == "true"; - transformAsync.transformLibraries( - new TypeEnvironment(coreTypes, hierarchy), libraries, - productMode: productMode, desugarAsync: !flags.compactAsync); - logger?.call("Transformed async methods"); - - lowering.transformLibraries( - libraries, coreTypes, hierarchy, flags.enableNullSafety); + lowering.transformLibraries(libraries, coreTypes, hierarchy, + nullSafety: flags.enableNullSafety, productMode: productMode); logger?.call("Lowering transformations performed"); callSiteAnnotator.transformLibraries( @@ -207,13 +198,8 @@ class VmTarget extends Target { Map? environmentDefines, {void Function(String msg)? logger}) { bool productMode = environmentDefines!["dart.vm.product"] == "true"; - transformAsync.transformProcedure( - new TypeEnvironment(coreTypes, hierarchy), procedure, - productMode: productMode, desugarAsync: !flags.compactAsync); - logger?.call("Transformed async functions"); - - lowering.transformProcedure( - procedure, coreTypes, hierarchy, flags.enableNullSafety); + lowering.transformProcedure(procedure, coreTypes, hierarchy, + nullSafety: flags.enableNullSafety, productMode: productMode); logger?.call("Lowering transformations performed"); } diff --git a/pkg/vm/lib/transformations/async.dart b/pkg/vm/lib/transformations/async.dart deleted file mode 100644 index 6a6628b4a3b..00000000000 --- a/pkg/vm/lib/transformations/async.dart +++ /dev/null @@ -1,672 +0,0 @@ -// Copyright (c) 2016, 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 'package:kernel/kernel.dart'; -import 'package:kernel/type_environment.dart'; -import 'continuation.dart'; - -/// A transformer that introduces temporary variables for all subexpressions -/// that are alive across yield points (AwaitExpression). -/// -/// The transformer is invoked by passing [rewrite] a top-level expression. -/// -/// All intermediate values that are possible live across an await are named in -/// local variables. -/// -/// Await expressions are translated into a call to a helper function and a -/// native yield. -class ExpressionLifter extends Transformer { - final AsyncRewriterBase continuationRewriter; - - /// Have we seen an await to the right in the expression tree. - /// - /// Subexpressions are visited right-to-left in the reverse of evaluation - /// order. - /// - /// On entry to an expression's visit method, [seenAwait] indicates whether a - /// sibling to the right contains an await. If so the expression will be - /// named in a temporary variable because it is potentially live across an - /// await. - /// - /// On exit from an expression's visit method, [seenAwait] indicates whether - /// the expression itself or a sibling to the right contains an await. - bool seenAwait = false; - - /// The (reverse order) sequence of statements that have been emitted. - /// - /// Transformation of an expression produces a transformed expression and a - /// sequence of statements which are assignments to local variables, calls to - /// helper functions, and yield points. Only the yield points need to be a - /// statements, and they are statements so an implementation does not have to - /// handle unnamed expression intermediate live across yield points. - /// - /// The visit methods return the transformed expression and build a sequence - /// of statements by emitting statements into this list. This list is built - /// in reverse because children are visited right-to-left. - /// - /// If an expression should be named it is named before visiting its children - /// so the naming assignment appears in the list before all statements - /// implementing the translation of the children. - /// - /// Children that are conditionally evaluated, such as some parts of logical - /// and conditional expressions, must be delimited so that they do not emit - /// unguarded statements into [statements]. This is implemented by setting - /// [statements] to a fresh empty list before transforming those children. - List statements = []; - - /// The number of currently live named intermediate values. - /// - /// This index is used to allocate names to temporary values. Because - /// children are visited right-to-left, names are assigned in reverse order of - /// index. - /// - /// When an assignment is emitted into [statements] to name an expression - /// before visiting its children, the index is not immediately reserved - /// because a child can freely use the same name as its parent. In practice, - /// this will be the rightmost named child. - /// - /// After visiting the children of a named expression, [nameIndex] is set to - /// indicate one more live value (the value of the expression) than before - /// visiting the expression. - /// - /// After visiting the children of an expression that is not named, - /// [nameIndex] may still account for names of subexpressions. - int nameIndex = 0; - - final VariableDeclaration asyncResult = - new VariableDeclaration(':result_or_exception'); - final List variables = []; - - ExpressionLifter(this.continuationRewriter); - - StatefulStaticTypeContext get _staticTypeContext => - continuationRewriter.staticTypeContext; - - Block blockOf(List statements) { - return new Block(statements.reversed.toList()); - } - - /// Rewrite a toplevel expression (toplevel wrt. a statement). - /// - /// Rewriting an expression produces a sequence of statements and an - /// expression. The sequence of statements are added to the given list. Pass - /// an empty list if the rewritten expression should be delimited from the - /// surrounding context. - Expression rewrite(Expression expression, List outer) { - assert(statements.isEmpty); - var saved = seenAwait; - seenAwait = false; - Expression result = transform(expression); - outer.addAll(statements.reversed); - statements.clear(); - seenAwait = seenAwait || saved; - return result; - } - - // Perform an action with a given list of statements so that it cannot emit - // statements into the 'outer' list. - Expression delimit(Expression action(), List inner) { - var outer = statements; - statements = inner; - Expression result = action(); - statements = outer; - return result; - } - - // Wraps VariableGet in an unsafeCast if `type` isn't dynamic. - Expression unsafeCastVariableGet( - VariableDeclaration variable, DartType type) { - if (type != const DynamicType()) { - return StaticInvocation( - continuationRewriter.helper.unsafeCast, - Arguments([VariableGet(variable)], - types: [type])); - } - return VariableGet(variable); - } - - // Name an expression by emitting an assignment to a temporary variable. - Expression name(Expression expr) { - DartType type = expr.getStaticType(_staticTypeContext); - VariableDeclaration temp = allocateTemporary(nameIndex, type); - statements.add(ExpressionStatement(VariableSet(temp, expr))); - // Wrap in unsafeCast to make sure we pass type information even if we later - // have to re-type the temporary variable to dynamic. - return unsafeCastVariableGet(temp, type); - } - - VariableDeclaration allocateTemporary(int index, - [DartType type = const DynamicType()]) { - if (variables.length > index) { - // Re-type temporary to dynamic if we detect reuse with different type. - // Note: We should make sure all uses use `unsafeCast(...)` to pass their - // type information on, as that is lost otherwise. - if (variables[index].type != const DynamicType() && - variables[index].type != type) { - variables[index].type = const DynamicType(); - } - return variables[index]; - } - for (var i = variables.length; i <= index; i++) { - variables.add(VariableDeclaration(":async_temporary_${i}", type: type)); - } - return variables[index]; - } - - // Simple literals. These are pure expressions so they can be evaluated after - // an await to their right. - @override - TreeNode visitSymbolLiteral(SymbolLiteral expr) => expr; - @override - TreeNode visitTypeLiteral(TypeLiteral expr) => expr; - @override - TreeNode visitThisExpression(ThisExpression expr) => expr; - @override - TreeNode visitStringLiteral(StringLiteral expr) => expr; - @override - TreeNode visitIntLiteral(IntLiteral expr) => expr; - @override - TreeNode visitDoubleLiteral(DoubleLiteral expr) => expr; - @override - TreeNode visitBoolLiteral(BoolLiteral expr) => expr; - @override - TreeNode visitNullLiteral(NullLiteral expr) => expr; - - // Nullary expressions with effects. - Expression nullary(Expression expr) { - if (seenAwait) { - expr = name(expr); - ++nameIndex; - } - return expr; - } - - @override - TreeNode visitSuperPropertyGet(SuperPropertyGet expr) => nullary(expr); - @override - TreeNode visitStaticGet(StaticGet expr) => nullary(expr); - @override - TreeNode visitStaticTearOff(StaticTearOff expr) => nullary(expr); - @override - TreeNode visitRethrow(Rethrow expr) => nullary(expr); - - // Getting a final or const variable is not an effect so it can be evaluated - // after an await to its right. - @override - TreeNode visitVariableGet(VariableGet expr) { - Expression result = expr; - if (seenAwait && !expr.variable.isFinal && !expr.variable.isConst) { - result = name(expr); - ++nameIndex; - } - return result; - } - - // Transform an expression given an action to transform the children. For - // this purposes of the await transformer the children should generally be - // translated from right to left, in the reverse of evaluation order. - Expression transformTreeNode(Expression expr, void action()) { - var shouldName = seenAwait; - - // 1. If there is an await in a sibling to the right, emit an assignment to - // a temporary variable before transforming the children. - var result = shouldName ? name(expr) : expr; - - // 2. Remember the number of live temporaries before transforming the - // children. - var index = nameIndex; - - // 3. Transform the children. Initially they do not have an await in a - // sibling to their right. - seenAwait = false; - action(); - - // 4. If the expression was named then the variables used for children are - // no longer live but the variable used for the expression is. - // On the other hand, a sibling to the left (yet to be processed) cannot - // reuse any of the variables used here, as the assignments in the children - // (here) would overwrite assignments in the siblings to the left, - // possibly before the use of the overwritten values. - if (shouldName) { - if (index + 1 > nameIndex) nameIndex = index + 1; - seenAwait = true; - } - return result; - } - - // Unary expressions. - Expression unary(Expression expr) { - return transformTreeNode(expr, () { - expr.transformChildren(this); - }); - } - - @override - TreeNode visitInvalidExpression(InvalidExpression expr) => unary(expr); - @override - TreeNode visitVariableSet(VariableSet expr) => unary(expr); - @override - TreeNode visitInstanceGet(InstanceGet expr) => unary(expr); - @override - TreeNode visitDynamicGet(DynamicGet expr) => unary(expr); - @override - TreeNode visitInstanceTearOff(InstanceTearOff expr) => unary(expr); - @override - TreeNode visitFunctionTearOff(FunctionTearOff expr) => unary(expr); - @override - TreeNode visitSuperPropertySet(SuperPropertySet expr) => unary(expr); - @override - TreeNode visitStaticSet(StaticSet expr) => unary(expr); - @override - TreeNode visitNot(Not expr) => unary(expr); - @override - TreeNode visitIsExpression(IsExpression expr) => unary(expr); - @override - TreeNode visitAsExpression(AsExpression expr) => unary(expr); - @override - TreeNode visitThrow(Throw expr) => unary(expr); - - @override - TreeNode visitInstanceSet(InstanceSet expr) { - return transformTreeNode(expr, () { - expr.value = transform(expr.value)..parent = expr; - expr.receiver = transform(expr.receiver)..parent = expr; - }); - } - - @override - TreeNode visitDynamicSet(DynamicSet expr) { - return transformTreeNode(expr, () { - expr.value = transform(expr.value)..parent = expr; - expr.receiver = transform(expr.receiver)..parent = expr; - }); - } - - @override - TreeNode visitArguments(Arguments args) { - for (var named in args.named.reversed) { - named.value = transform(named.value)..parent = named; - } - var positional = args.positional; - for (var i = positional.length - 1; i >= 0; --i) { - positional[i] = transform(positional[i])..parent = args; - } - // Returns the arguments, which is assumed at the call sites because they do - // not replace the arguments or set parent pointers. - return args; - } - - @override - TreeNode visitInstanceInvocation(InstanceInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - expr.receiver = transform(expr.receiver)..parent = expr; - }); - } - - @override - TreeNode visitLocalFunctionInvocation(LocalFunctionInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - }); - } - - @override - TreeNode visitDynamicInvocation(DynamicInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - expr.receiver = transform(expr.receiver)..parent = expr; - }); - } - - @override - TreeNode visitFunctionInvocation(FunctionInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - expr.receiver = transform(expr.receiver)..parent = expr; - }); - } - - @override - TreeNode visitEqualsNull(EqualsNull expr) => unary(expr); - - @override - TreeNode visitEqualsCall(EqualsCall expr) { - return transformTreeNode(expr, () { - expr.right = transform(expr.right)..parent = expr; - expr.left = transform(expr.left)..parent = expr; - }); - } - - @override - TreeNode visitSuperMethodInvocation(SuperMethodInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - }); - } - - @override - TreeNode visitStaticInvocation(StaticInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - }); - } - - @override - TreeNode visitConstructorInvocation(ConstructorInvocation expr) { - return transformTreeNode(expr, () { - visitArguments(expr.arguments); - }); - } - - @override - TreeNode visitStringConcatenation(StringConcatenation expr) { - return transformTreeNode(expr, () { - var expressions = expr.expressions; - for (var i = expressions.length - 1; i >= 0; --i) { - expressions[i] = transform(expressions[i])..parent = expr; - } - }); - } - - @override - TreeNode visitListLiteral(ListLiteral expr) { - return transformTreeNode(expr, () { - var expressions = expr.expressions; - for (var i = expressions.length - 1; i >= 0; --i) { - expressions[i] = transform(expr.expressions[i])..parent = expr; - } - }); - } - - @override - TreeNode visitMapLiteral(MapLiteral expr) { - return transformTreeNode(expr, () { - for (var entry in expr.entries.reversed) { - entry.value = transform(entry.value)..parent = entry; - entry.key = transform(entry.key)..parent = entry; - } - }); - } - - // Control flow. - @override - TreeNode visitLogicalExpression(LogicalExpression expr) { - var shouldName = seenAwait; - - // Right is delimited because it is conditionally evaluated. - var rightStatements = []; - seenAwait = false; - expr.right = delimit(() => transform(expr.right), rightStatements) - ..parent = expr; - var rightAwait = seenAwait; - - if (rightStatements.isEmpty) { - // Easy case: right did not emit any statements. - seenAwait = shouldName; - return transformTreeNode(expr, () { - expr.left = transform(expr.left)..parent = expr; - seenAwait = seenAwait || rightAwait; - }); - } - - // If right has emitted statements we will produce a temporary t and emit - // for && (there is an analogous case for ||): - // - // t = [left] == true; - // if (t) { - // t = [right] == true; - // } - - // Recall that statements are emitted in reverse order, so first emit the if - // statement, then the assignment of [left] == true, and then translate left - // so any statements it emits occur after in the accumulated list (that is, - // so they occur before in the corresponding block). - var rightBody = blockOf(rightStatements); - final type = _staticTypeContext.typeEnvironment.coreTypes - .boolRawType(_staticTypeContext.nonNullable); - final result = allocateTemporary(nameIndex, type); - final objectEquals = continuationRewriter.helper.coreTypes.objectEquals; - rightBody.addStatement(new ExpressionStatement(new VariableSet( - result, - new EqualsCall(expr.right, new BoolLiteral(true), - interfaceTarget: objectEquals, - functionType: objectEquals.getterType as FunctionType)))); - Statement then; - Statement? otherwise; - if (expr.operatorEnum == LogicalExpressionOperator.AND) { - then = rightBody; - otherwise = null; - } else { - then = new EmptyStatement(); - otherwise = rightBody; - } - statements.add( - new IfStatement(unsafeCastVariableGet(result, type), then, otherwise)); - - final test = new EqualsCall(expr.left, new BoolLiteral(true), - interfaceTarget: objectEquals, - functionType: objectEquals.getterType as FunctionType); - statements.add(new ExpressionStatement(new VariableSet(result, test))); - - seenAwait = false; - test.left = transform(test.left)..parent = test; - - ++nameIndex; - seenAwait = seenAwait || rightAwait; - return unsafeCastVariableGet(result, type); - } - - @override - TreeNode visitConditionalExpression(ConditionalExpression expr) { - // Then and otherwise are delimited because they are conditionally - // evaluated. - var shouldName = seenAwait; - - final savedNameIndex = nameIndex; - - var thenStatements = []; - seenAwait = false; - expr.then = delimit(() => transform(expr.then), thenStatements) - ..parent = expr; - var thenAwait = seenAwait; - - final thenNameIndex = nameIndex; - nameIndex = savedNameIndex; - - var otherwiseStatements = []; - seenAwait = false; - expr.otherwise = - delimit(() => transform(expr.otherwise), otherwiseStatements) - ..parent = expr; - var otherwiseAwait = seenAwait; - - // Only one side of this branch will get executed at a time, so just make - // sure we have enough temps for either, not both at the same time. - if (thenNameIndex > nameIndex) { - nameIndex = thenNameIndex; - } - - if (thenStatements.isEmpty && otherwiseStatements.isEmpty) { - // Easy case: neither then nor otherwise emitted any statements. - seenAwait = shouldName; - return transformTreeNode(expr, () { - expr.condition = transform(expr.condition)..parent = expr; - seenAwait = seenAwait || thenAwait || otherwiseAwait; - }); - } - - // If `then` or `otherwise` has emitted statements we will produce a - // temporary t and emit: - // - // if ([condition]) { - // t = [left]; - // } else { - // t = [right]; - // } - final result = allocateTemporary(nameIndex, expr.staticType); - var thenBody = blockOf(thenStatements); - var otherwiseBody = blockOf(otherwiseStatements); - thenBody.addStatement( - new ExpressionStatement(new VariableSet(result, expr.then))); - otherwiseBody.addStatement( - new ExpressionStatement(new VariableSet(result, expr.otherwise))); - var branch = new IfStatement(expr.condition, thenBody, otherwiseBody); - statements.add(branch); - - seenAwait = false; - branch.condition = transform(branch.condition)..parent = branch; - - ++nameIndex; - seenAwait = seenAwait || thenAwait || otherwiseAwait; - return unsafeCastVariableGet(result, expr.staticType); - } - - // Others. - @override - TreeNode visitAwaitExpression(AwaitExpression expr) { - final R = continuationRewriter; - var shouldName = seenAwait; - var type = expr.getStaticType(_staticTypeContext); - Expression result = unsafeCastVariableGet(asyncResult, type); - - // The statements are in reverse order, so name the result first if - // necessary and then add the two other statements in reverse. - if (shouldName) result = name(result); - Arguments arguments = new Arguments([ - expr.operand, - new VariableGet(R.thenContinuationVariable), - new VariableGet(R.catchErrorContinuationVariable), - ]); - - // We are building - // - // [yield] (let _ = _awaitHelper(...) in null) - // - // to ensure that :await_jump_var and :await_jump_ctx are updated - // before _awaitHelper is invoked (see BuildYieldStatement in - // StreamingFlowGraphBuilder for details of how [yield] is translated to - // IL). This guarantees that recursive invocation of the current function - // would continue from the correct "jump" position. Recursive invocations - // arise if future we are awaiting completes synchronously. Builtin Future - // implementation don't complete synchronously, but Flutter's - // SynchronousFuture do (see bug http://dartbug.com/32098 for more details). - statements.add(R.createContinuationPoint(new Let( - new VariableDeclaration(null, - initializer: new StaticInvocation(R.helper.awaitHelper, arguments) - ..fileOffset = expr.fileOffset), - new NullLiteral())) - ..fileOffset = expr.fileOffset); - - seenAwait = false; - var index = nameIndex; - arguments.positional[0] = transform(expr.operand)..parent = arguments; - - if (shouldName && index + 1 > nameIndex) nameIndex = index + 1; - seenAwait = true; - return result; - } - - @override - TreeNode visitFunctionExpression(FunctionExpression expr) { - expr.transformChildren(this); - return expr; - } - - @override - TreeNode visitLet(Let expr) { - var body = transform(expr.body); - - VariableDeclaration variable = expr.variable; - if (seenAwait) { - // There is an await in the body of `let var x = initializer in body` or - // to its right. We will produce the sequence of statements: - // - // - // var x = - // - // - // and return the body's value. - // - // So x is in scope for all the body's statements and the body's value. - // This has the unpleasant consequence that all let-bound variables with - // await in the let's body will end up hoisted out of the expression and - // allocated to the context in the VM, even if they have no uses - // (`let _ = e0 in e1` can be used for sequencing of `e0` and `e1`). - statements.add(variable); - var index = nameIndex; - seenAwait = false; - variable.initializer = transform(variable.initializer!) - ..parent = variable; - // Temporaries used in the initializer or the body are not live but the - // temporary used for the body is. - if (index + 1 > nameIndex) nameIndex = index + 1; - seenAwait = true; - return body; - } else { - // The body in `let x = initializer in body` did not contain an await. We - // can leave a let expression. - return transformTreeNode(expr, () { - // The body has already been translated. - expr.body = body..parent = expr; - variable.initializer = transform(variable.initializer!) - ..parent = variable; - }); - } - } - - @override - TreeNode visitFunctionNode(FunctionNode node) { - var nestedRewriter = new RecursiveContinuationRewriter( - continuationRewriter.helper, - _staticTypeContext, - continuationRewriter.desugarAsync); - return nestedRewriter.transform(node); - } - - @override - TreeNode visitBlockExpression(BlockExpression expr) { - return transformTreeNode(expr, () { - expr.value = transform(expr.value)..parent = expr; - List body = []; - for (Statement stmt in expr.body.statements.reversed) { - Statement? translation = _rewriteStatement(stmt); - if (translation != null) body.add(translation); - } - expr.body = new Block(body.reversed.toList())..parent = expr; - }); - } - - Statement? _rewriteStatement(Statement stmt) { - // This method translates a statement nested in an expression (e.g., in a - // block expression). It produces a translated statement, a list of - // statements which are side effects necessary for any await, and a flag - // indicating whether there was an await in the statement or to its right. - // The translated statement can be null in the case where there was already - // an await to the right. - - // The translation is accumulating two lists of statements, an inner list - // which is a reversed list of effects needed for the current expression and - // an outer list which represents the block containing the current - // statement. We need to preserve both of those from side effects. - List savedInner = statements; - List savedOuter = continuationRewriter.statements; - statements = []; - continuationRewriter.statements = []; - continuationRewriter.transform(stmt); - - List results = continuationRewriter.statements; - statements = savedInner; - continuationRewriter.statements = savedOuter; - if (!seenAwait && results.length == 1) return results.first; - statements.addAll(results.reversed); - return null; - } - - @override - TreeNode defaultStatement(Statement stmt) { - throw new UnsupportedError( - "Use _rewriteStatement to transform statement: ${stmt}"); - } -} diff --git a/pkg/vm/lib/transformations/continuation.dart b/pkg/vm/lib/transformations/continuation.dart deleted file mode 100644 index d23954fe58b..00000000000 --- a/pkg/vm/lib/transformations/continuation.dart +++ /dev/null @@ -1,1667 +0,0 @@ -// Copyright (c) 2016, 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:math' as math; - -import 'package:kernel/ast.dart'; -import 'package:kernel/core_types.dart'; -import 'package:kernel/type_algebra.dart' show Substitution; -import 'package:kernel/type_environment.dart'; - -import 'async.dart'; - -class ContinuationVariables { - static const awaitJumpVar = ':await_jump_var'; - static const asyncFuture = ':async_future'; - static const isSync = ":is_sync"; - static const awaitContextVar = ':await_ctx_var'; - static const asyncCompleter = ':async_completer'; - static const asyncOp = ':async_op'; - static const asyncOpThen = ':async_op_then'; - static const asyncOpError = ':async_op_error'; - static const controller = ':controller'; - static const controllerStreamVar = ':controller_stream'; - static const forIterator = ':for-iterator'; - static const returnValue = ':return_value'; - static const stream = ':stream'; - static const syncForIterator = ':sync-for-iterator'; - static const syncOpGen = ':sync_op_gen'; - static const syncOp = ':sync_op'; - // sync_op(..) parameter. - static const iteratorParam = ':iterator'; - // (a)sync_op(..) parameters. - static const exceptionParam = ':exception'; - static const stackTraceParam = ':stack_trace'; - - static const savedTryContextVarPrefix = ':saved_try_context_var'; - static const exceptionVarPrefix = ':exception'; - static const stackTraceVarPrefix = ':stack_trace'; - - static String savedTryContextVar(int depth) => - '$savedTryContextVarPrefix$depth'; - static String exceptionVar(int depth) => '$exceptionVarPrefix$depth'; - static String stackTraceVar(int depth) => '$stackTraceVarPrefix$depth'; -} - -void transformLibraries( - TypeEnvironment typeEnvironment, List libraries, - {required bool productMode, required bool desugarAsync}) { - var helper = - new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode); - var rewriter = new RecursiveContinuationRewriter(helper, - new StatefulStaticTypeContext.stacked(typeEnvironment), desugarAsync); - for (var library in libraries) { - rewriter.rewriteLibrary(library); - } -} - -Component transformComponent( - TypeEnvironment typeEnvironment, Component component, - {required bool productMode, required bool desugarAsync}) { - var helper = - new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode); - var rewriter = new RecursiveContinuationRewriter(helper, - new StatefulStaticTypeContext.stacked(typeEnvironment), desugarAsync); - return rewriter.rewriteComponent(component); -} - -Procedure transformProcedure( - TypeEnvironment typeEnvironment, Procedure procedure, - {required bool productMode, required bool desugarAsync}) { - var helper = - new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode); - var rewriter = new RecursiveContinuationRewriter(helper, - new StatefulStaticTypeContext.stacked(typeEnvironment), desugarAsync); - return rewriter.transform(procedure); -} - -class RecursiveContinuationRewriter extends RemovingTransformer { - final HelperNodes helper; - - final VariableDeclaration awaitJumpVariable = new VariableDeclaration( - ContinuationVariables.awaitJumpVar, - initializer: new IntLiteral(0)); - final VariableDeclaration awaitContextVariable = - new VariableDeclaration(ContinuationVariables.awaitContextVar); - - StatefulStaticTypeContext staticTypeContext; - final bool desugarAsync; - final bool desugarAwaitFor; - - RecursiveContinuationRewriter( - this.helper, this.staticTypeContext, this.desugarAsync, - {this.desugarAwaitFor = false}); - - Component rewriteComponent(Component node) { - return transform(node); - } - - Library rewriteLibrary(Library node) { - return transform(node); - } - - @override - TreeNode visitField(Field node, TreeNode? removalSentinel) { - staticTypeContext.enterMember(node); - final result = super.visitField(node, removalSentinel); - staticTypeContext.leaveMember(node); - return result; - } - - @override - TreeNode visitConstructor(Constructor node, TreeNode? removalSentinel) { - staticTypeContext.enterMember(node); - final result = super.visitConstructor(node, removalSentinel); - staticTypeContext.leaveMember(node); - return result; - } - - @override - TreeNode visitProcedure(Procedure node, TreeNode? removalSentinel) { - staticTypeContext.enterMember(node); - final result = - node.isAbstract ? node : super.visitProcedure(node, removalSentinel); - staticTypeContext.leaveMember(node); - return result; - } - - @override - TreeNode visitLibrary(Library node, TreeNode? removalSentinel) { - staticTypeContext.enterLibrary(node); - Library result = super.visitLibrary(node, removalSentinel) as Library; - staticTypeContext.leaveLibrary(node); - return result; - } - - @override - TreeNode visitFunctionNode(FunctionNode node, TreeNode? removalSentinel) { - switch (node.asyncMarker) { - case AsyncMarker.Sync: - case AsyncMarker.SyncYielding: - node.transformOrRemoveChildren(new RecursiveContinuationRewriter( - helper, staticTypeContext, desugarAsync)); - return node; - case AsyncMarker.SyncStar: - if (desugarAsync) { - return new SyncStarFunctionRewriter( - helper, node, staticTypeContext, desugarAsync) - .rewrite(); - } else { - node.transformOrRemoveChildren(new RecursiveContinuationRewriter( - helper, staticTypeContext, desugarAsync)); - return node; - } - case AsyncMarker.Async: - if (desugarAsync) { - return new AsyncFunctionRewriter( - helper, node, staticTypeContext, desugarAsync) - .rewrite(); - } else { - node.transformOrRemoveChildren(new RecursiveContinuationRewriter( - helper, staticTypeContext, desugarAsync, - desugarAwaitFor: true)); - return node; - } - case AsyncMarker.AsyncStar: - if (desugarAsync) { - return new AsyncStarFunctionRewriter( - helper, node, staticTypeContext, desugarAsync) - .rewrite(); - } else { - node.transformOrRemoveChildren(new RecursiveContinuationRewriter( - helper, staticTypeContext, desugarAsync, - desugarAwaitFor: true)); - return node; - } - } - } - - @override - TreeNode visitForInStatement(ForInStatement stmt, TreeNode? removalSentinel) { - if (stmt.isAsync) { - if (!desugarAwaitFor) { - return super.visitForInStatement(stmt, removalSentinel); - } - // Transform - // - // await for (T variable in ) { ... } - // - // To (in product mode): - // - // { - // :stream = ; - // _StreamIterator :for-iterator = new _StreamIterator(:stream); - // try { - // while (await :for-iterator.moveNext()) { - // T = :for-iterator.current; - // ... - // } - // } finally { - // if (:for-iterator._subscription != null) - // await :for-iterator.cancel(); - // } - // } - // - // Or (in non-product mode): - // - // { - // :stream = ; - // _StreamIterator :for-iterator = new _StreamIterator(:stream); - // try { - // while (let _ = _asyncStarMoveNextHelper(:stream) in - // await :for-iterator.moveNext()) { - // T = :for-iterator.current; - // ... - // } - // } finally { - // if (:for-iterator._subscription != null) - // await :for-iterator.cancel(); - // } - // } - var valueVariable = stmt.variable; - - var streamVariable = new VariableDeclaration(ContinuationVariables.stream, - initializer: stmt.iterable, - type: stmt.iterable.getStaticType(staticTypeContext)); - - final streamIteratorType = new InterfaceType(helper.streamIteratorClass, - staticTypeContext.nullable, [valueVariable.type]); - var forIteratorVariable = VariableDeclaration( - ContinuationVariables.forIterator, - initializer: new ConstructorInvocation( - helper.streamIteratorConstructor, - new Arguments([new VariableGet(streamVariable)], - types: [valueVariable.type])), - type: streamIteratorType); - - // await :for-iterator.moveNext() - var condition = new AwaitExpression(new InstanceInvocation( - InstanceAccessKind.Instance, - VariableGet(forIteratorVariable), - helper.streamIteratorMoveNext.name, - new Arguments([]), - interfaceTarget: helper.streamIteratorMoveNext, - functionType: - helper.streamIteratorMoveNext.getterType as FunctionType)) - ..fileOffset = stmt.fileOffset; - - Expression whileCondition; - if (helper.productMode) { - whileCondition = condition; - } else { - // _asyncStarMoveNextHelper(:stream) - var asyncStarMoveNextCall = new StaticInvocation( - helper.asyncStarMoveNextHelper, - new Arguments([new VariableGet(streamVariable)])) - ..fileOffset = stmt.fileOffset; - - // let _ = asyncStarMoveNextCall in (condition) - whileCondition = new Let( - new VariableDeclaration(null, initializer: asyncStarMoveNextCall), - condition); - } - - // T = :for-iterator.current; - valueVariable.initializer = new InstanceGet(InstanceAccessKind.Instance, - VariableGet(forIteratorVariable), helper.streamIteratorCurrent.name, - interfaceTarget: helper.streamIteratorCurrent, - resultType: valueVariable.type) - ..fileOffset = stmt.bodyOffset; - valueVariable.initializer!.parent = valueVariable; - - var whileBody = new Block([valueVariable, stmt.body]); - var tryBody = new WhileStatement(whileCondition, whileBody); - - // if (:for-iterator._subscription != null) await :for-iterator.cancel(); - final DartType subscriptionType = - Substitution.fromInterfaceType(streamIteratorType).substituteType( - helper.coreTypes.streamIteratorSubscription.getterType); - var tryFinalizer = new IfStatement( - new Not(new EqualsNull(new InstanceGet( - InstanceAccessKind.Instance, - VariableGet(forIteratorVariable), - helper.coreTypes.streamIteratorSubscription.name, - interfaceTarget: helper.coreTypes.streamIteratorSubscription, - resultType: subscriptionType))), - new ExpressionStatement(new AwaitExpression(new InstanceInvocation( - InstanceAccessKind.Instance, - VariableGet(forIteratorVariable), - helper.streamIteratorCancel.name, - new Arguments([]), - interfaceTarget: helper.streamIteratorCancel, - functionType: - helper.streamIteratorCancel.getterType as FunctionType))), - null); - - var tryFinally = new TryFinally(tryBody, tryFinalizer); - - var block = new Block( - [streamVariable, forIteratorVariable, tryFinally]); - return transform(block); - } - - // Transform - // - // for ({var/final} T in ) { ... } - // - // Into - // - // { - // final Iterator :sync-for-iterator = .iterator; - // for (; :sync-for-iterator.moveNext() ;) { - // {var/final} T variable = :sync-for-iterator.current; - // ... - // } - // } - // } - final CoreTypes coreTypes = staticTypeContext.typeEnvironment.coreTypes; - - // The CFE might invoke this transformation despite the program having - // compile-time errors. So we will not transform this [stmt] if the - // `stmt.iterable` is an invalid expression or has an invalid type and - // instead eliminate the entire for-in and replace it with a invalid - // expression statement. - final iterable = stmt.iterable; - final iterableType = iterable.getStaticType(staticTypeContext); - if (iterableType is InvalidType) { - return ExpressionStatement( - InvalidExpression('Invalid iterable type in for-in')); - } - - // The NNBD sdk declares that Iterable.get:iterator returns a non-nullable - // `Iterator`. - assert(const [ - Nullability.nonNullable, - Nullability.legacy - ].contains(coreTypes.iterableGetIterator.function.returnType.nullability)); - - final DartType elementType = stmt.getElementType(staticTypeContext); - final iteratorType = InterfaceType( - coreTypes.iteratorClass, staticTypeContext.nonNullable, [elementType]); - - final syncForIterator = VariableDeclaration( - ContinuationVariables.syncForIterator, - initializer: InstanceGet(InstanceAccessKind.Instance, iterable, - coreTypes.iterableGetIterator.name, - interfaceTarget: coreTypes.iterableGetIterator, - resultType: iteratorType) - ..fileOffset = iterable.fileOffset, - type: iteratorType) - ..fileOffset = iterable.fileOffset; - - final condition = InstanceInvocation( - InstanceAccessKind.Instance, - VariableGet(syncForIterator), - coreTypes.iteratorMoveNext.name, - Arguments([]), - interfaceTarget: coreTypes.iteratorMoveNext, - functionType: coreTypes.iteratorMoveNext.getterType as FunctionType) - ..fileOffset = iterable.fileOffset; - - final variable = stmt.variable - ..initializer = (InstanceGet(InstanceAccessKind.Instance, - VariableGet(syncForIterator), coreTypes.iteratorGetCurrent.name, - interfaceTarget: coreTypes.iteratorGetCurrent, - resultType: elementType) - ..fileOffset = stmt.bodyOffset); - - final Block body = Block([variable, stmt.body]) - ..fileOffset = stmt.bodyOffset; - - return transform( - Block([syncForIterator, ForStatement([], condition, [], body)])); - } -} - -abstract class ContinuationRewriterBase extends RecursiveContinuationRewriter { - final FunctionNode enclosingFunction; - - int currentTryDepth = 0; // Nesting depth for try-blocks. - int currentCatchDepth = 0; // Nesting depth for catch-blocks. - 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, - StatefulStaticTypeContext staticTypeContext, bool desugarAsync, - {bool desugarAwaitFor = false}) - : super(helper, staticTypeContext, desugarAsync, - desugarAwaitFor: desugarAwaitFor); - - /// Given a container [type], which is an instantiation of the given - /// [containerClass] extract its element type. - /// - /// This is used to extract element type from Future, Iterable and - /// Stream instantiations. - /// - /// If instantiation is not valid (has more than 1 type argument) then - /// this function returns [InvalidType]. - static DartType elementTypeFrom(Class containerClass, DartType type) { - if (type is InterfaceType) { - if (type.classNode == containerClass) { - if (type.typeArguments.isEmpty) { - return const DynamicType(); - } else if (type.typeArguments.length == 1) { - return type.typeArguments[0]; - } else { - return const InvalidType(); - } - } - } - return const DynamicType(); - } - - static DartType elementTypeFromFutureOr(DartType type) { - if (type is FutureOrType) { - return type.typeArgument; - } - return const DynamicType(); - } - - DartType elementTypeFromReturnType(Class expected) => - elementTypeFrom(expected, enclosingFunction.returnType); - - DartType elementTypeFromAsyncReturnType() => - elementTypeFromFutureOr(enclosingFunction.returnType); - - Statement createContinuationPoint([Expression? value]) { - if (value == null) value = new NullLiteral(); - capturedTryDepth = math.max(capturedTryDepth, currentTryDepth); - capturedCatchDepth = math.max(capturedCatchDepth, currentCatchDepth); - return new YieldStatement(value, isNative: true); - } - - @override - TreeNode visitTryCatch(TryCatch node, TreeNode? removalSentinel) { - // ignore: unnecessary_null_comparison - if (node.body != null) { - ++currentTryDepth; - node.body = transform(node.body); - node.body.parent = node; - --currentTryDepth; - } - - ++currentCatchDepth; - transformCatchList(node.catches, node); - --currentCatchDepth; - return node; - } - - @override - TreeNode visitTryFinally(TryFinally node, TreeNode? removalSentinel) { - // ignore: unnecessary_null_comparison - if (node.body != null) { - ++currentTryDepth; - node.body = transform(node.body); - node.body.parent = node; - --currentTryDepth; - } - // ignore: unnecessary_null_comparison - if (node.finalizer != null) { - ++currentCatchDepth; - node.finalizer = transform(node.finalizer); - node.finalizer.parent = node; - --currentCatchDepth; - } - return node; - } - - Iterable createCapturedTryVariables() => - new Iterable.generate( - capturedTryDepth, - (depth) => new VariableDeclaration( - ContinuationVariables.savedTryContextVar(depth))); - - Iterable createCapturedCatchVariables() => - new Iterable.generate(capturedCatchDepth).expand((depth) => [ - new VariableDeclaration(ContinuationVariables.exceptionVar(depth)), - new VariableDeclaration(ContinuationVariables.stackTraceVar(depth)), - ]); - - List variableDeclarations() { - awaitJumpVariable.type = staticTypeContext.typeEnvironment.coreTypes - .intRawType(staticTypeContext.nonNullable); - return [awaitJumpVariable, awaitContextVariable] - ..addAll(createCapturedTryVariables()) - ..addAll(createCapturedCatchVariables()); - } -} - -// Transformer that rewrites all variable references to a given function's -// parameters. -// This allows us to e.g. "shadow" the original parameter variables with copies -// unique to given sub-closure to prevent shared variables being overwritten. -class ShadowRewriter extends Transformer { - final FunctionNode enclosingFunction; - Map _shadowedParameters = {}; - - ShadowRewriter(this.enclosingFunction) { - for (final parameter in enclosingFunction.positionalParameters - .followedBy(enclosingFunction.namedParameters)) { - // Put in placeholders so we can allocate new variables lazily- i.e. only - // if they're later referenced. - _shadowedParameters[parameter] = null; - } - } - - // Return all used parameters. - Iterable get shadowedParameters => - _shadowedParameters.values.whereType(); - - VariableDeclaration _rewrite(VariableDeclaration variable) { - if (_shadowedParameters.containsKey(variable)) { - // Fill in placeholder. - VariableDeclaration? placeholder = _shadowedParameters[variable]; - if (placeholder == null) { - placeholder = _shadowedParameters[variable] = VariableDeclaration( - variable.name, - type: variable.type, - initializer: VariableGet(variable), - ); - } - variable = placeholder; - } - return variable; - } - - @override - TreeNode visitVariableGet(VariableGet node) { - node = super.visitVariableGet(node) as VariableGet; - return node..variable = _rewrite(node.variable); - } - - @override - TreeNode visitVariableSet(VariableSet node) { - node = super.visitVariableSet(node) as VariableSet; - return node..variable = _rewrite(node.variable); - } -} - -class SyncStarFunctionRewriter extends ContinuationRewriterBase { - final VariableDeclaration iteratorParameter; - - SyncStarFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction, - StatefulStaticTypeContext staticTypeContext, bool desugarAsync) - : iteratorParameter = - VariableDeclaration(ContinuationVariables.iteratorParam) - ..type = InterfaceType( - helper.syncIteratorClass, staticTypeContext.nullable, [ - // Note: This is dynamic since nested iterators (of potentially - // different type) are handled by shared internal synthetic - // code. - const DynamicType(), - ]), - super(helper, enclosingFunction, staticTypeContext, desugarAsync, - desugarAwaitFor: false); - - FunctionNode rewrite() { - // We need to preserve the original parameters passed to the sync* function - // because each iteration should start from those parameters. To achieve - // this we shadow the original parameters with new variables (which are - // initialised to the original parameter values) and rewrite - // the body to use these variables instead. - final shadowRewriter = ShadowRewriter(enclosingFunction); - enclosingFunction.body = shadowRewriter.transform(enclosingFunction.body!) - ..parent = enclosingFunction; - - // TODO(cskau): Figure out why inlining this below causes segfaults. - // Maybe related to http://dartbug.com/41596 ? - final syncOpFN = FunctionNode(buildClosureBody(), - positionalParameters: [ - iteratorParameter, - new VariableDeclaration(ContinuationVariables.exceptionParam), - new VariableDeclaration(ContinuationVariables.stackTraceParam), - ], - requiredParameterCount: 3, - // Note: SyncYielding functions have no Dart equivalent. Since they are - // synchronous, we use Sync. (Note also that the Dart VM backend uses - // the Dart async marker to decide if functions are debuggable.) - asyncMarker: AsyncMarker.SyncYielding, - dartAsyncMarker: AsyncMarker.Sync, - returnType: helper.coreTypes.boolLegacyRawType) - ..fileOffset = enclosingFunction.fileOffset - ..fileEndOffset = enclosingFunction.fileEndOffset; - final syncOpType = - syncOpFN.computeThisFunctionType(staticTypeContext.nonNullable); - - final syncOpGenVariable = VariableDeclaration( - ContinuationVariables.syncOpGen, - type: FunctionType([], syncOpType, staticTypeContext.nonNullable)); - - final syncOpVariable = VariableDeclaration(ContinuationVariables.syncOp); - final syncOpDecl = FunctionDeclaration(syncOpVariable, syncOpFN) - ..fileOffset = enclosingFunction.fileOffset; - - enclosingFunction.body = Block([ - // :sync_op_gen() { - // :await_jump_var; - // :await_ctx_var; - // bool sync_op(:iterator, e, st) yielding { - // modified ... - // }; - // return sync_op; - // } - FunctionDeclaration( - syncOpGenVariable, - FunctionNode( - Block([ - // :await_jump_var, :await_ctx_var. - ...variableDeclarations(), - // Shadow any used function parameters with local copies. - ...shadowRewriter.shadowedParameters, - // :sync_op(..) { .. } - syncOpDecl, - // return sync_op; - ReturnStatement(VariableGet(syncOpVariable)), - ]), - returnType: syncOpType)) - ..fileOffset = enclosingFunction.fileOffset, - - // return _SyncIterable(:sync_op_gen); - ReturnStatement(ConstructorInvocation( - helper.syncIterableConstructor, - Arguments([ - VariableGet(syncOpGenVariable) - ], types: [ - ContinuationRewriterBase.elementTypeFrom( - helper.iterableClass, enclosingFunction.returnType) - ]))), - ]) - ..parent = enclosingFunction; - enclosingFunction.asyncMarker = AsyncMarker.Sync; - - return enclosingFunction; - } - - Statement buildClosureBody() { - // The body will insert calls to - // :iterator.current_= - // :iterator.isYieldEach= - // and return `true` as long as it did something and `false` when it's done. - return new Block([ - transform(enclosingFunction.body!), - new ReturnStatement(new BoolLiteral(false)) - ..fileOffset = enclosingFunction.fileEndOffset - ]); - } - - @override - TreeNode visitYieldStatement(YieldStatement node, TreeNode? removalSentinel) { - Expression transformedExpression = transform(node.expression); - - var statements = []; - if (node.isYieldStar) { - statements.add(new ExpressionStatement(new InstanceSet( - InstanceAccessKind.Instance, - VariableGet(iteratorParameter), - helper.syncIteratorYieldEachIterable.name, - transformedExpression, - interfaceTarget: helper.syncIteratorYieldEachIterable))); - } else { - statements.add(new ExpressionStatement(new InstanceSet( - InstanceAccessKind.Instance, - VariableGet(iteratorParameter), - helper.syncIteratorCurrent.name, - transformedExpression, - interfaceTarget: helper.syncIteratorCurrent))); - } - - statements.add(createContinuationPoint(new BoolLiteral(true)) - ..fileOffset = node.fileOffset); - return new Block(statements); - } - - @override - TreeNode visitReturnStatement( - ReturnStatement node, TreeNode? removalSentinel) { - // sync* functions cannot return a value. - assert(node.expression == null || node.expression is NullLiteral); - node.expression = new BoolLiteral(false)..parent = node; - return node; - } -} - -abstract class AsyncRewriterBase extends ContinuationRewriterBase { - // :async_op has type (dynamic result_or_exception, StackTrace? s) -> dynamic - final VariableDeclaration nestedClosureVariable; - - // :async_op_then has type (dynamic result) -> dynamic - final VariableDeclaration thenContinuationVariable; - - // :async_op_error has type (Object e, StackTrace s) -> dynamic - final VariableDeclaration catchErrorContinuationVariable; - - LabeledStatement? labeledBody; - - ExpressionLifter? expressionRewriter; - - AsyncRewriterBase(HelperNodes helper, FunctionNode enclosingFunction, - StatefulStaticTypeContext staticTypeContext, bool desugarAsync) - : nestedClosureVariable = - VariableDeclaration(ContinuationVariables.asyncOp, - type: FunctionType([ - const DynamicType(), - helper.coreTypes - .stackTraceRawType(staticTypeContext.nullable), - ], const DynamicType(), staticTypeContext.nonNullable)), - thenContinuationVariable = VariableDeclaration( - ContinuationVariables.asyncOpThen, - type: FunctionType(const [const DynamicType()], const DynamicType(), - staticTypeContext.nonNullable)), - catchErrorContinuationVariable = - VariableDeclaration(ContinuationVariables.asyncOpError, - type: FunctionType([ - helper.coreTypes.objectRawType(staticTypeContext.nonNullable), - helper.coreTypes - .stackTraceRawType(staticTypeContext.nonNullable), - ], const DynamicType(), staticTypeContext.nonNullable)), - super(helper, enclosingFunction, staticTypeContext, desugarAsync, - desugarAwaitFor: true) {} - - void setupAsyncContinuations(List statements) { - expressionRewriter = new ExpressionLifter(this); - - // var :async_op_then; - statements.add(thenContinuationVariable); - - // var :async_op_error; - statements.add(catchErrorContinuationVariable); - - // :async_op(:result_or_exception, :stack_trace) { - // modified ; - // } - final parameters = [ - expressionRewriter!.asyncResult, - new VariableDeclaration(ContinuationVariables.stackTraceParam), - ]; - - // Note: SyncYielding functions have no Dart equivalent. Since they are - // synchronous, we use Sync. (Note also that the Dart VM backend uses the - // Dart async marker to decide if functions are debuggable.) - final function = new FunctionNode(buildWrappedBody(), - positionalParameters: parameters, - asyncMarker: AsyncMarker.SyncYielding, - dartAsyncMarker: AsyncMarker.Sync) - ..fileOffset = enclosingFunction.fileOffset - ..fileEndOffset = enclosingFunction.fileEndOffset; - - // The await expression lifter might have created a number of - // [VariableDeclarations]. - // TODO(kustermann): If we didn't need any variables we should not emit - // these. - statements.addAll(variableDeclarations()); - statements.addAll(expressionRewriter!.variables); - - // Now add the closure function itself. - final closureFunction = - new FunctionDeclaration(nestedClosureVariable, function) - ..fileOffset = enclosingFunction.parent!.fileOffset; - statements.add(closureFunction); - - // :async_op_then = _asyncThenWrapperHelper(asyncBody); - final boundThenClosure = new StaticInvocation(helper.asyncThenWrapper, - new Arguments([new VariableGet(nestedClosureVariable)])); - final thenClosureVariableAssign = new ExpressionStatement( - new VariableSet(thenContinuationVariable, boundThenClosure)); - statements.add(thenClosureVariableAssign); - - // :async_op_error = _asyncErrorWrapperHelper(asyncBody); - final boundCatchErrorClosure = new StaticInvocation( - helper.asyncErrorWrapper, - new Arguments([new VariableGet(nestedClosureVariable)])); - final catchErrorClosureVariableAssign = new ExpressionStatement( - new VariableSet( - catchErrorContinuationVariable, boundCatchErrorClosure)); - statements.add(catchErrorClosureVariableAssign); - } - - Statement buildWrappedBody() { - ++currentTryDepth; - labeledBody = new LabeledStatement(null); - labeledBody!.body = visitDelimited(enclosingFunction.body!) - ..parent = labeledBody; - --currentTryDepth; - - var exceptionVariable = VariableDeclaration('exception'); - var stackTraceVariable = VariableDeclaration('stack_trace', - type: - helper.coreTypes.stackTraceRawType(staticTypeContext.nonNullable)); - - return new TryCatch( - buildReturn(labeledBody!), - [ - new Catch( - exceptionVariable, - new Block([ - buildCatchBody(exceptionVariable, stackTraceVariable) - ]), - stackTrace: stackTraceVariable) - ], - isSynthetic: true, - ); - } - - Statement buildCatchBody(VariableDeclaration exceptionVariable, - VariableDeclaration stackTraceVariable); - - Statement buildReturn(Statement body); - - List statements = []; - - @override - TreeNode visitExpressionStatement( - ExpressionStatement stmt, TreeNode? removalSentinel) { - stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements) - ..parent = stmt; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitBlock(Block stmt, TreeNode? removalSentinel) { - var saved = statements; - statements = []; - for (var statement in stmt.statements) { - transform(statement); - } - saved.add(new Block(statements)); - statements = saved; - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitEmptyStatement(EmptyStatement stmt, TreeNode? removalSentinel) { - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitAssertBlock(AssertBlock stmt, TreeNode? removalSentinel) { - var saved = statements; - statements = []; - for (var statement in stmt.statements) { - transform(statement); - } - saved.add(new Block(statements)); - statements = saved; - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitAssertStatement( - AssertStatement stmt, TreeNode? removalSentinel) { - var condEffects = []; - var cond = expressionRewriter!.rewrite(stmt.condition, condEffects); - if (stmt.message == null) { - stmt.condition = cond..parent = stmt; - // If the translation of the condition produced a non-empty list of - // statements, ensure they are guarded by whether asserts are enabled. - statements.add( - condEffects.isEmpty ? stmt : new AssertBlock(condEffects..add(stmt))); - return removalSentinel ?? EmptyStatement(); - } - - // The translation depends on the translation of the message, by cases. - Statement result; - var msgEffects = []; - stmt.message = expressionRewriter!.rewrite(stmt.message!, msgEffects) - ..parent = stmt; - if (condEffects.isEmpty) { - if (msgEffects.isEmpty) { - // The condition rewrote to ([], C) and the message rewrote to ([], M). - // The result is - // - // assert(C, M) - stmt.condition = cond..parent = stmt; - result = stmt; - } else { - // The condition rewrote to ([], C) and the message rewrote to (S*, M) - // where S* is non-empty. The result is - // - // assert { if (C) {} else { S*; assert(false, M); }} - stmt.condition = new BoolLiteral(false)..parent = stmt; - result = new AssertBlock([ - new IfStatement( - cond, new EmptyStatement(), new Block(msgEffects..add(stmt))) - ]); - } - } else { - if (msgEffects.isEmpty) { - // The condition rewrote to (S*, C) where S* is non-empty and the - // message rewrote to ([], M). The result is - // - // assert { S*; assert(C, M); } - stmt.condition = cond..parent = stmt; - condEffects.add(stmt); - } else { - // The condition rewrote to (S0*, C) and the message rewrote to (S1*, M) - // where both S0* and S1* are non-empty. The result is - // - // assert { S0*; if (C) {} else { S1*; assert(false, M); }} - stmt.condition = new BoolLiteral(false)..parent = stmt; - condEffects.add(new IfStatement( - cond, new EmptyStatement(), new Block(msgEffects..add(stmt)))); - } - result = new AssertBlock(condEffects); - } - statements.add(result); - return removalSentinel ?? EmptyStatement(); - } - - Statement visitDelimited(Statement stmt) { - var saved = statements; - statements = []; - transform(stmt); - Statement result = - statements.length == 1 ? statements.first : new Block(statements); - statements = saved; - return result; - } - - @override - TreeNode visitLabeledStatement( - LabeledStatement stmt, TreeNode? removalSentinel) { - stmt.body = visitDelimited(stmt.body)..parent = stmt; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitBreakStatement(BreakStatement stmt, TreeNode? removalSentinel) { - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitWhileStatement(WhileStatement stmt, TreeNode? removalSentinel) { - Statement body = visitDelimited(stmt.body); - List effects = []; - Expression cond = expressionRewriter!.rewrite(stmt.condition, effects); - if (effects.isEmpty) { - stmt.condition = cond..parent = stmt; - stmt.body = body..parent = stmt; - statements.add(stmt); - } else { - // The condition rewrote to a non-empty sequence of statements S* and - // value V. Rewrite the loop to: - // - // L: while (true) { - // S* - // if (V) { - // [body] - // else { - // break L; - // } - // } - LabeledStatement labeled = new LabeledStatement(stmt); - stmt.condition = new BoolLiteral(true)..parent = stmt; - effects.add(new IfStatement(cond, body, new BreakStatement(labeled))); - stmt.body = new Block(effects)..parent = stmt; - statements.add(labeled); - } - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitDoStatement(DoStatement stmt, TreeNode? removalSentinel) { - Statement body = visitDelimited(stmt.body); - List effects = []; - stmt.condition = expressionRewriter!.rewrite(stmt.condition, effects) - ..parent = stmt; - if (effects.isNotEmpty) { - // The condition rewrote to a non-empty sequence of statements S* and - // value V. Add the statements to the end of the loop body. - Block block = body is Block ? body : body = new Block([body]); - for (var effect in effects) { - block.statements.add(effect); - effect.parent = body; - } - } - stmt.body = body..parent = stmt; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitForStatement(ForStatement stmt, TreeNode? removalSentinel) { - // Because of for-loop scoping and variable capture, it is tricky to deal - // with await in the loop's variable initializers or update expressions. - bool isSimple = true; - int length = stmt.variables.length; - List> initEffects = - new List>.generate(length, (int i) { - VariableDeclaration decl = stmt.variables[i]; - List statements = []; - if (decl.initializer != null) { - decl.initializer = expressionRewriter! - .rewrite(decl.initializer!, statements) - ..parent = decl; - } - isSimple = isSimple && statements.isEmpty; - return statements; - }); - - length = stmt.updates.length; - List> updateEffects = - new List>.generate(length, (int i) { - List statements = []; - stmt.updates[i] = expressionRewriter!.rewrite(stmt.updates[i], statements) - ..parent = stmt; - isSimple = isSimple && statements.isEmpty; - return statements; - }); - - Statement body = visitDelimited(stmt.body); - Expression? cond = stmt.condition; - List? condEffects; - if (cond != null) { - condEffects = []; - cond = expressionRewriter!.rewrite(stmt.condition!, condEffects); - } - - if (isSimple) { - // If the condition contains await, we use a translation like the one for - // while loops, but leaving the variable declarations and the update - // expressions in place. - if (condEffects == null || condEffects.isEmpty) { - if (cond != null) stmt.condition = cond..parent = stmt; - stmt.body = body..parent = stmt; - statements.add(stmt); - } else { - LabeledStatement labeled = new LabeledStatement(stmt); - // No condition in a for loop is the same as true. - stmt.condition = null; - condEffects - .add(new IfStatement(cond!, body, new BreakStatement(labeled))); - stmt.body = new Block(condEffects)..parent = stmt; - statements.add(labeled); - } - return removalSentinel ?? EmptyStatement(); - } - - // If the rewrite of the initializer or update expressions produces a - // non-empty sequence of statements then the loop is desugared. If the loop - // has the form: - // - // label: for (Type x = init; cond; update) body - // - // it is translated as if it were: - // - // { - // bool first = true; - // Type temp; - // label: while (true) { - // Type x; - // if (first) { - // first = false; - // x = init; - // } else { - // x = temp; - // update; - // } - // if (cond) { - // body; - // temp = x; - // } else { - // break; - // } - // } - // } - - // Place the loop variable declarations at the beginning of the body - // statements and move their initializers to a guarded list of statements. - // Add assignments to the loop variables from the previous iterations temp - // variables before the updates. - // - // temps.first is the flag 'first'. - // TODO(kmillikin) bool type for first. - List temps = [ - new VariableDeclaration.forValue(new BoolLiteral(true), isFinal: false) - ]; - List loopBody = []; - List initializers = [ - new ExpressionStatement( - new VariableSet(temps.first, new BoolLiteral(false))) - ]; - List updates = []; - List newBody = [body]; - for (int i = 0; i < stmt.variables.length; ++i) { - VariableDeclaration decl = stmt.variables[i]; - temps.add(new VariableDeclaration(null, type: decl.type)); - loopBody.add(decl); - if (decl.initializer != null) { - initializers.addAll(initEffects[i]); - initializers.add( - new ExpressionStatement(new VariableSet(decl, decl.initializer!))); - decl.initializer = null; - } - updates.add(new ExpressionStatement( - new VariableSet(decl, new VariableGet(temps.last)))); - newBody.add(new ExpressionStatement( - new VariableSet(temps.last, new VariableGet(decl)))); - } - // Add the updates to their guarded list of statements. - for (int i = 0; i < stmt.updates.length; ++i) { - updates.addAll(updateEffects[i]); - updates.add(new ExpressionStatement(stmt.updates[i])); - } - // Initializers or updates could be empty. - loopBody.add(new IfStatement(new VariableGet(temps.first), - new Block(initializers), new Block(updates))); - - LabeledStatement labeled = new LabeledStatement(null); - if (cond != null) { - loopBody.addAll(condEffects!); - } else { - cond = new BoolLiteral(true); - } - loopBody.add( - new IfStatement(cond, new Block(newBody), new BreakStatement(labeled))); - labeled.body = - new WhileStatement(new BoolLiteral(true), new Block(loopBody)) - ..parent = labeled; - statements.add(new Block([] - ..addAll(temps) - ..add(labeled))); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitSwitchStatement( - SwitchStatement stmt, TreeNode? removalSentinel) { - stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements) - ..parent = stmt; - for (var switchCase in stmt.cases) { - // Expressions in switch cases cannot contain await so they do not need to - // be translated. - switchCase.body = visitDelimited(switchCase.body)..parent = switchCase; - } - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitContinueSwitchStatement( - ContinueSwitchStatement stmt, TreeNode? removalSentinel) { - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitIfStatement(IfStatement stmt, TreeNode? removalSentinel) { - stmt.condition = expressionRewriter!.rewrite(stmt.condition, statements) - ..parent = stmt; - stmt.then = visitDelimited(stmt.then)..parent = stmt; - if (stmt.otherwise != null) { - stmt.otherwise = visitDelimited(stmt.otherwise!)..parent = stmt; - } - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitTryCatch(TryCatch stmt, TreeNode? removalSentinel) { - ++currentTryDepth; - stmt.body = visitDelimited(stmt.body)..parent = stmt; - --currentTryDepth; - - ++currentCatchDepth; - for (var clause in stmt.catches) { - clause.body = visitDelimited(clause.body)..parent = clause; - } - --currentCatchDepth; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitTryFinally(TryFinally stmt, TreeNode? removalSentinel) { - ++currentTryDepth; - stmt.body = visitDelimited(stmt.body)..parent = stmt; - --currentTryDepth; - ++currentCatchDepth; - stmt.finalizer = visitDelimited(stmt.finalizer)..parent = stmt; - --currentCatchDepth; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitYieldStatement(YieldStatement stmt, TreeNode? removalSentinel) { - stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements) - ..parent = stmt; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitVariableDeclaration( - VariableDeclaration stmt, TreeNode? removalSentinel) { - if (stmt.initializer != null) { - stmt.initializer = expressionRewriter! - .rewrite(stmt.initializer!, statements) - ..parent = stmt; - } - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitFunctionDeclaration( - FunctionDeclaration stmt, TreeNode? removalSentinel) { - stmt.function = transform(stmt.function)..parent = stmt; - statements.add(stmt); - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode defaultExpression(TreeNode node, TreeNode? removalSentinel) => - throw 'unreachable $node'; -} - -class AsyncStarFunctionRewriter extends AsyncRewriterBase { - VariableDeclaration? controllerVariable; - - AsyncStarFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction, - StatefulStaticTypeContext staticTypeContext, bool desugarAsync) - : super(helper, enclosingFunction, staticTypeContext, desugarAsync); - - FunctionNode rewrite() { - var statements = []; - - final elementType = elementTypeFromReturnType(helper.streamClass); - - // _AsyncStarStreamController :controller; - controllerVariable = new VariableDeclaration( - ContinuationVariables.controller, - type: new InterfaceType(helper.asyncStarStreamControllerClass, - staticTypeContext.nullable, [elementType])); - statements.add(controllerVariable!); - - // dynamic :controller_stream; - VariableDeclaration controllerStreamVariable = - new VariableDeclaration(ContinuationVariables.controllerStreamVar); - statements.add(controllerStreamVariable); - - setupAsyncContinuations(statements); - - // :controller = new _AsyncStarStreamController(:async_op); - var arguments = new Arguments( - [new VariableGet(nestedClosureVariable)], - types: [elementType]); - var buildController = new ConstructorInvocation( - helper.asyncStarStreamControllerConstructor, arguments) - ..fileOffset = enclosingFunction.fileOffset; - var setController = new ExpressionStatement( - new VariableSet(controllerVariable!, buildController)); - statements.add(setController); - - // :controller_stream = :controller.stream; - var completerGet = new VariableGet(controllerVariable!); - statements.add(new ExpressionStatement(new VariableSet( - controllerStreamVariable, - new InstanceGet(InstanceAccessKind.Instance, completerGet, - helper.asyncStarStreamControllerStream.name, - interfaceTarget: helper.asyncStarStreamControllerStream, - resultType: Substitution.fromInterfaceType( - controllerVariable!.type as InterfaceType) - .substituteType( - helper.asyncStarStreamControllerStream.getterType))))); - - // return :controller_stream; - var returnStatement = - new ReturnStatement(new VariableGet(controllerStreamVariable)); - statements.add(returnStatement); - - enclosingFunction.body = new Block(statements)..parent = enclosingFunction; - enclosingFunction.asyncMarker = AsyncMarker.Sync; - return enclosingFunction; - } - - @override - Statement buildWrappedBody() { - ++currentTryDepth; - Statement body = super.buildWrappedBody(); - --currentTryDepth; - - var finallyBody = new ExpressionStatement(new InstanceInvocation( - InstanceAccessKind.Instance, - new VariableGet(controllerVariable!), - helper.asyncStarStreamControllerClose.name, - new Arguments([]), - interfaceTarget: helper.asyncStarStreamControllerClose, - functionType: - helper.asyncStarStreamControllerClose.getterType as FunctionType)); - - var tryFinally = new TryFinally(body, new Block([finallyBody])); - return tryFinally; - } - - @override - Statement buildCatchBody(VariableDeclaration exceptionVariable, - VariableDeclaration stackTraceVariable) { - return new ExpressionStatement(new InstanceInvocation( - InstanceAccessKind.Instance, - new VariableGet(controllerVariable!), - helper.asyncStarStreamControllerAddError.name, - new Arguments([ - new VariableGet(exceptionVariable), - new VariableGet(stackTraceVariable) - ]), - interfaceTarget: helper.asyncStarStreamControllerAddError, - functionType: helper.asyncStarStreamControllerAddError.getterType - as FunctionType)); - } - - @override - Statement buildReturn(Statement body) { - // Async* functions cannot return a value. The returns from the function - // have been translated into breaks from the labeled body. - return new Block([ - body, - new ReturnStatement()..fileOffset = enclosingFunction.fileEndOffset, - ]); - } - - @override - TreeNode visitYieldStatement(YieldStatement stmt, TreeNode? removalSentinel) { - Expression expr = expressionRewriter!.rewrite(stmt.expression, statements); - - final Procedure addMethod = stmt.isYieldStar - ? helper.asyncStarStreamControllerAddStream - : helper.asyncStarStreamControllerAdd; - final FunctionType addMethodFunctionType = Substitution.fromInterfaceType( - controllerVariable!.type as InterfaceType) - .substituteType(addMethod.getterType) as FunctionType; - var addExpression = new InstanceInvocation( - InstanceAccessKind.Instance, - new VariableGet(controllerVariable!), - addMethod.name, - new Arguments([expr]), - interfaceTarget: addMethod, - functionType: addMethodFunctionType) - ..fileOffset = stmt.fileOffset; - - if (stmt.isYieldStar) { - statements.add(ExpressionStatement(addExpression)); - statements.add(createContinuationPoint()..fileOffset = stmt.fileOffset); - final wasCancelled = StaticInvocation( - helper.unsafeCast, - Arguments([VariableGet(expressionRewriter!.asyncResult)], - types: [helper.coreTypes.boolNonNullableRawType])); - statements - .add(IfStatement(wasCancelled, ReturnStatement(NullLiteral()), null)); - } else { - statements.add(new IfStatement( - addExpression, - new ReturnStatement(new NullLiteral()), - createContinuationPoint()..fileOffset = stmt.fileOffset)); - } - return removalSentinel ?? EmptyStatement(); - } - - @override - TreeNode visitReturnStatement( - ReturnStatement node, TreeNode? removalSentinel) { - // Async* functions cannot return a value. - assert(node.expression == null || node.expression is NullLiteral); - statements - .add(new BreakStatement(labeledBody!)..fileOffset = node.fileOffset); - return removalSentinel ?? EmptyStatement(); - } -} - -class AsyncFunctionRewriter extends AsyncRewriterBase { - VariableDeclaration? returnVariable; - VariableDeclaration? asyncFutureVariable; - VariableDeclaration? isSyncVariable; - - // In general an async functions such as - // - // Future foo() async { return ; } - // - // can return as `` either X or Future, i.e. it can return - // FutureOr - // - // If we know it doesn't return any object of type `Future`, we can optimize - // the future completion process by avoiding some expensive `is Future` - // type checks on the returned value. - late bool canReturnFuture; - - AsyncFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction, - StatefulStaticTypeContext staticTypeContext, bool desugarAsync) - : super(helper, enclosingFunction, staticTypeContext, desugarAsync); - - FunctionNode rewrite() { - var statements = []; - - // The original function return type should be Future or FutureOr - // because the function is async. If it was, we make a Completer, - // otherwise we make a malformed type. In a "Future foo() async {}" - // function the body can either return a "T" or a "Future" => a - // "FutureOr". - DartType valueType = elementTypeFromReturnType(helper.futureClass); - if (valueType == const DynamicType()) { - valueType = elementTypeFromAsyncReturnType(); - } - final DartType returnType = - FutureOrType(valueType, staticTypeContext.nullable); - final futureTypeArguments = [valueType]; - - final futureType = InterfaceType(helper.futureImplClass, - staticTypeContext.nonNullable, futureTypeArguments); - - // final _Future :async_future = _Future(); - asyncFutureVariable = VariableDeclaration(ContinuationVariables.asyncFuture, - initializer: ConstructorInvocation(helper.futureImplConstructor, - Arguments([], types: futureTypeArguments)) - ..fileOffset = enclosingFunction.body?.fileOffset ?? -1, - isFinal: true, - type: futureType); - statements.add(asyncFutureVariable!); - - // bool :is_sync = false; - isSyncVariable = VariableDeclaration(ContinuationVariables.isSync, - initializer: BoolLiteral(false), - type: helper.coreTypes.boolLegacyRawType); - statements.add(isSyncVariable!); - - // asy::FutureOr* :return_value; - returnVariable = VariableDeclaration(ContinuationVariables.returnValue, - type: returnType); - statements.add(returnVariable!); - - canReturnFuture = false; - - setupAsyncContinuations(statements); - - // If we could prove the function doesn't return a `Future` we change the - // type of `:return_value`. - if (!canReturnFuture) { - returnVariable!.type = - valueType.withDeclaredNullability(Nullability.nullable); - } - - // :async_op(null, null); - final startStatement = ExpressionStatement(LocalFunctionInvocation( - nestedClosureVariable, Arguments([NullLiteral(), NullLiteral()]), - functionType: FunctionType( - [], const DynamicType(), staticTypeContext.nonNullable)) - ..fileOffset = enclosingFunction.fileOffset); - statements.add(startStatement); - - // :is_sync = true; - final setIsSync = - ExpressionStatement(VariableSet(isSyncVariable!, BoolLiteral(true))); - statements.add(setIsSync); - - // return :async_future; - statements.add(ReturnStatement(VariableGet(asyncFutureVariable!))); - - enclosingFunction.body = Block(statements)..parent = enclosingFunction; - enclosingFunction.asyncMarker = AsyncMarker.Sync; - return enclosingFunction; - } - - // :async_op's try-catch catch body: - @override - Statement buildCatchBody(exceptionVariable, stackTraceVariable) { - // _completeOnAsyncError(_future, e, st, :is_sync) - return ExpressionStatement(StaticInvocation( - helper.completeOnAsyncError, - Arguments([ - VariableGet(asyncFutureVariable!), - VariableGet(exceptionVariable), - VariableGet(stackTraceVariable), - VariableGet(isSyncVariable!) - ]))); - } - - // :async_op's try-catch try body: - @override - Statement buildReturn(Statement body) { - // Returns from the body have all been translated into assignments to the - // return value variable followed by a break from the labeled body. - - // .. body .. - // _completeOnAsyncReturn(_future, returnVariable, :is_sync) - // return; - return Block([ - body, - ExpressionStatement(StaticInvocation( - canReturnFuture - ? helper.completeOnAsyncReturn - : helper.completeWithNoFutureOnAsyncReturn, - Arguments([ - VariableGet(asyncFutureVariable!), - VariableGet(returnVariable!), - VariableGet(isSyncVariable!) - ]))), - ReturnStatement()..fileOffset = enclosingFunction.fileEndOffset - ]); - } - - @override - TreeNode visitReturnStatement( - ReturnStatement node, TreeNode? removalSentinel) { - final expression = node.expression; - if (expression != null && !canReturnFuture) { - final returnedType = staticTypeContext.getExpressionType(expression); - canReturnFuture = _canHoldFutureObject(returnedType); - } - - final transformedExpression = node.expression == null - ? NullLiteral() - : expressionRewriter!.rewrite(node.expression!, statements); - statements.add(ExpressionStatement( - VariableSet(returnVariable!, transformedExpression) - ..fileOffset = node.fileOffset)); - statements.add(BreakStatement(labeledBody!)); - return removalSentinel ?? EmptyStatement(); - } - - bool _canHoldFutureObject(DartType type) { - // Any supertype or subtype of `FutureOr` may hold a `Future` object. - final env = staticTypeContext.typeEnvironment; - - if (type is TypeParameterType) { - type = type.parameter.defaultType; - } - - if (type is FutureOrType) return true; - - // Any supertype of Future (which includes Future/Object/dynamic) can hold - // Future objects. - if (env.isSubtypeOf( - helper.futureType, type, SubtypeCheckMode.ignoringNullabilities)) { - return true; - } - - // Any subtype of Future (which includes Future/_Future and any user-defined - // implementations) can hold Future objects. - if (env.isSubtypeOf( - type, helper.futureType, SubtypeCheckMode.ignoringNullabilities)) { - return true; - } - return false; - } -} - -class HelperNodes { - final Procedure asyncErrorWrapper; - final Library asyncLibrary; - final Procedure asyncStarStreamControllerAdd; - final Procedure asyncStarStreamControllerAddError; - final Procedure asyncStarStreamControllerAddStream; - final Class asyncStarStreamControllerClass; - final Procedure asyncStarStreamControllerClose; - final Constructor asyncStarStreamControllerConstructor; - final Member asyncStarStreamControllerStream; - final Procedure asyncStarMoveNextHelper; - final Procedure asyncThenWrapper; - final Procedure awaitHelper; - final Procedure completeOnAsyncReturn; - final Procedure completeWithNoFutureOnAsyncReturn; - final Procedure completeOnAsyncError; - final Library coreLibrary; - final CoreTypes coreTypes; - final Class futureClass; - final Class futureOrClass; - final Class futureImplClass; - final Constructor futureImplConstructor; - final Class iterableClass; - final Class streamClass; - final Procedure streamIteratorCancel; - final Class streamIteratorClass; - final Constructor streamIteratorConstructor; - final Member streamIteratorCurrent; - final Procedure streamIteratorMoveNext; - final Constructor syncIterableConstructor; - final Class syncIteratorClass; - final Member syncIteratorCurrent; - final Member syncIteratorYieldEachIterable; - final Class boolClass; - final Procedure unsafeCast; - final DartType futureType; - - bool productMode; - - HelperNodes._( - this.asyncErrorWrapper, - this.asyncLibrary, - this.asyncStarStreamControllerAdd, - this.asyncStarStreamControllerAddError, - this.asyncStarStreamControllerAddStream, - this.asyncStarStreamControllerClass, - this.asyncStarStreamControllerClose, - this.asyncStarStreamControllerConstructor, - this.asyncStarStreamControllerStream, - this.asyncStarMoveNextHelper, - this.asyncThenWrapper, - this.awaitHelper, - this.completeOnAsyncReturn, - this.completeWithNoFutureOnAsyncReturn, - this.completeOnAsyncError, - this.coreLibrary, - this.coreTypes, - this.futureClass, - this.futureOrClass, - this.futureImplClass, - this.futureImplConstructor, - this.iterableClass, - this.streamClass, - this.streamIteratorCancel, - this.streamIteratorClass, - this.streamIteratorConstructor, - this.streamIteratorCurrent, - this.streamIteratorMoveNext, - this.syncIterableConstructor, - this.syncIteratorClass, - this.syncIteratorCurrent, - this.syncIteratorYieldEachIterable, - this.boolClass, - this.productMode, - this.unsafeCast) - : futureType = InterfaceType( - futureClass, Nullability.nonNullable, [DynamicType()]); - - factory HelperNodes.fromCoreTypes(CoreTypes coreTypes, bool productMode) { - return new HelperNodes._( - coreTypes.asyncErrorWrapperHelperProcedure, - coreTypes.asyncLibrary, - coreTypes.asyncStarStreamControllerAdd, - coreTypes.asyncStarStreamControllerAddError, - coreTypes.asyncStarStreamControllerAddStream, - coreTypes.asyncStarStreamControllerClass, - coreTypes.asyncStarStreamControllerClose, - coreTypes.asyncStarStreamControllerDefaultConstructor, - coreTypes.asyncStarStreamControllerStream, - coreTypes.asyncStarMoveNextHelper, - coreTypes.asyncThenWrapperHelperProcedure, - coreTypes.awaitHelperProcedure, - coreTypes.completeOnAsyncReturn, - coreTypes.completeWithNoFutureOnAsyncReturn, - coreTypes.completeOnAsyncError, - coreTypes.coreLibrary, - coreTypes, - coreTypes.futureClass, - coreTypes.deprecatedFutureOrClass, - coreTypes.futureImplClass, - coreTypes.futureImplConstructor, - coreTypes.iterableClass, - coreTypes.streamClass, - coreTypes.streamIteratorCancel, - coreTypes.streamIteratorClass, - coreTypes.streamIteratorDefaultConstructor, - coreTypes.streamIteratorCurrent, - coreTypes.streamIteratorMoveNext, - coreTypes.syncIterableDefaultConstructor, - coreTypes.syncIteratorClass, - coreTypes.syncIteratorCurrent, - coreTypes.syncIteratorYieldEachIterable, - coreTypes.boolClass, - productMode, - coreTypes.index.getTopLevelMember('dart:_internal', 'unsafeCast') - as Procedure); - } -} diff --git a/pkg/vm/lib/transformations/for_in_lowering.dart b/pkg/vm/lib/transformations/for_in_lowering.dart new file mode 100644 index 00000000000..f9576ed033c --- /dev/null +++ b/pkg/vm/lib/transformations/for_in_lowering.dart @@ -0,0 +1,222 @@ +// Copyright (c) 2022, 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 'package:kernel/ast.dart'; +import 'package:kernel/core_types.dart' show CoreTypes; +import 'package:kernel/type_algebra.dart' show Substitution; +import 'package:kernel/type_environment.dart' show StaticTypeContext; + +class ForInVariables { + static const stream = ':stream'; + static const forIterator = ':for-iterator'; + static const syncForIterator = ':sync-for-iterator'; +} + +/// VM-specific desugaring of for-in loops. +class ForInLowering { + final CoreTypes coreTypes; + final bool productMode; + + ForInLowering(this.coreTypes, {required this.productMode}); + + Statement transformForInStatement(ForInStatement stmt, + FunctionNode? enclosingFunction, StaticTypeContext staticTypeContext) { + if (stmt.isAsync) { + if (enclosingFunction == null || + (enclosingFunction.asyncMarker != AsyncMarker.Async && + enclosingFunction.asyncMarker != AsyncMarker.AsyncStar)) { + return stmt; + } + // Transform + // + // await for (T variable in ) { ... } + // + // To (in product mode): + // + // { + // :stream = ; + // _StreamIterator :for-iterator = new _StreamIterator(:stream); + // try { + // while (await :for-iterator.moveNext()) { + // T = :for-iterator.current; + // ... + // } + // } finally { + // if (:for-iterator._subscription != null) + // await :for-iterator.cancel(); + // } + // } + // + // Or (in non-product mode): + // + // { + // :stream = ; + // _StreamIterator :for-iterator = new _StreamIterator(:stream); + // try { + // while (let _ = _asyncStarMoveNextHelper(:stream) in + // await :for-iterator.moveNext()) { + // T = :for-iterator.current; + // ... + // } + // } finally { + // if (:for-iterator._subscription != null) + // await :for-iterator.cancel(); + // } + // } + final valueVariable = stmt.variable; + + final streamVariable = new VariableDeclaration(ForInVariables.stream, + initializer: stmt.iterable, + type: stmt.iterable.getStaticType(staticTypeContext)); + + final streamIteratorType = new InterfaceType( + coreTypes.streamIteratorClass, + staticTypeContext.nullable, + [valueVariable.type]); + final forIteratorVariable = VariableDeclaration( + ForInVariables.forIterator, + initializer: new ConstructorInvocation( + coreTypes.streamIteratorDefaultConstructor, + new Arguments([new VariableGet(streamVariable)], + types: [valueVariable.type])), + type: streamIteratorType); + + // await :for-iterator.moveNext() + final condition = new AwaitExpression(new InstanceInvocation( + InstanceAccessKind.Instance, + VariableGet(forIteratorVariable), + coreTypes.streamIteratorMoveNext.name, + new Arguments([]), + interfaceTarget: coreTypes.streamIteratorMoveNext, + functionType: + coreTypes.streamIteratorMoveNext.getterType as FunctionType)) + ..fileOffset = stmt.fileOffset; + + Expression whileCondition; + if (productMode) { + whileCondition = condition; + } else { + // _asyncStarMoveNextHelper(:stream) + final asyncStarMoveNextCall = new StaticInvocation( + coreTypes.asyncStarMoveNextHelper, + new Arguments([new VariableGet(streamVariable)])) + ..fileOffset = stmt.fileOffset; + + // let _ = asyncStarMoveNextCall in (condition) + whileCondition = new Let( + new VariableDeclaration(null, initializer: asyncStarMoveNextCall), + condition); + } + + // T = :for-iterator.current; + valueVariable.initializer = new InstanceGet( + InstanceAccessKind.Instance, + VariableGet(forIteratorVariable), + coreTypes.streamIteratorCurrent.name, + interfaceTarget: coreTypes.streamIteratorCurrent, + resultType: valueVariable.type) + ..fileOffset = stmt.bodyOffset; + valueVariable.initializer!.parent = valueVariable; + + final whileBody = new Block([valueVariable, stmt.body]); + final tryBody = new WhileStatement(whileCondition, whileBody); + + // if (:for-iterator._subscription != null) await :for-iterator.cancel(); + final DartType subscriptionType = + Substitution.fromInterfaceType(streamIteratorType) + .substituteType(coreTypes.streamIteratorSubscription.getterType); + final tryFinalizer = new IfStatement( + new Not(new EqualsNull(new InstanceGet( + InstanceAccessKind.Instance, + VariableGet(forIteratorVariable), + coreTypes.streamIteratorSubscription.name, + interfaceTarget: coreTypes.streamIteratorSubscription, + resultType: subscriptionType))), + new ExpressionStatement(new AwaitExpression(new InstanceInvocation( + InstanceAccessKind.Instance, + VariableGet(forIteratorVariable), + coreTypes.streamIteratorCancel.name, + new Arguments([]), + interfaceTarget: coreTypes.streamIteratorCancel, + functionType: + coreTypes.streamIteratorCancel.getterType as FunctionType))), + null); + + final tryFinally = new TryFinally(tryBody, tryFinalizer); + + final block = new Block( + [streamVariable, forIteratorVariable, tryFinally]); + return block; + } + + // Transform + // + // for ({var/final} T in ) { ... } + // + // Into + // + // { + // final Iterator :sync-for-iterator = .iterator; + // for (; :sync-for-iterator.moveNext() ;) { + // {var/final} T variable = :sync-for-iterator.current; + // ... + // } + // } + // } + + // The CFE might invoke this transformation despite the program having + // compile-time errors. So we will not transform this [stmt] if the + // `stmt.iterable` is an invalid expression or has an invalid type and + // instead eliminate the entire for-in and replace it with a invalid + // expression statement. + final iterable = stmt.iterable; + final iterableType = iterable.getStaticType(staticTypeContext); + if (iterableType is InvalidType) { + return ExpressionStatement( + InvalidExpression('Invalid iterable type in for-in')); + } + + // The NNBD sdk declares that Iterable.get:iterator returns a non-nullable + // `Iterator`. + assert(const [ + Nullability.nonNullable, + Nullability.legacy + ].contains(coreTypes.iterableGetIterator.function.returnType.nullability)); + + final DartType elementType = stmt.getElementType(staticTypeContext); + final iteratorType = InterfaceType( + coreTypes.iteratorClass, staticTypeContext.nonNullable, [elementType]); + + final syncForIterator = VariableDeclaration(ForInVariables.syncForIterator, + initializer: InstanceGet(InstanceAccessKind.Instance, iterable, + coreTypes.iterableGetIterator.name, + interfaceTarget: coreTypes.iterableGetIterator, + resultType: iteratorType) + ..fileOffset = iterable.fileOffset, + type: iteratorType) + ..fileOffset = iterable.fileOffset; + + final condition = InstanceInvocation( + InstanceAccessKind.Instance, + VariableGet(syncForIterator), + coreTypes.iteratorMoveNext.name, + Arguments([]), + interfaceTarget: coreTypes.iteratorMoveNext, + functionType: coreTypes.iteratorMoveNext.getterType as FunctionType) + ..fileOffset = iterable.fileOffset; + + final variable = stmt.variable + ..initializer = (InstanceGet(InstanceAccessKind.Instance, + VariableGet(syncForIterator), coreTypes.iteratorGetCurrent.name, + interfaceTarget: coreTypes.iteratorGetCurrent, + resultType: elementType) + ..fileOffset = stmt.bodyOffset); + variable.initializer!.parent = variable; + + final Block body = Block([variable, stmt.body]) + ..fileOffset = stmt.bodyOffset; + + return Block([syncForIterator, ForStatement([], condition, [], body)]); + } +} diff --git a/pkg/vm/lib/transformations/lowering.dart b/pkg/vm/lib/transformations/lowering.dart index 4242ff43ac5..f86ff95b40c 100644 --- a/pkg/vm/lib/transformations/lowering.dart +++ b/pkg/vm/lib/transformations/lowering.dart @@ -10,6 +10,7 @@ import 'package:kernel/type_environment.dart' import 'package:vm/transformations/specializer/factory_specializer.dart'; +import 'for_in_lowering.dart' show ForInLowering; import 'late_var_init_transformer.dart' show LateVarInitTransformer; import 'list_literals_lowering.dart' show ListLiteralsLowering; import 'type_casts_optimizer.dart' as typeCastsOptimizer @@ -20,15 +21,19 @@ import 'type_casts_optimizer.dart' as typeCastsOptimizer /// /// Each transformation is applied locally to AST nodes of certain types /// after transforming children nodes. -void transformLibraries(List libraries, CoreTypes coreTypes, - ClassHierarchy hierarchy, bool nullSafety) { - final transformer = _Lowering(coreTypes, hierarchy, nullSafety); +void transformLibraries( + List libraries, CoreTypes coreTypes, ClassHierarchy hierarchy, + {required bool nullSafety, required bool productMode}) { + final transformer = _Lowering(coreTypes, hierarchy, + nullSafety: nullSafety, productMode: productMode); libraries.forEach(transformer.visitLibrary); } -void transformProcedure(Procedure procedure, CoreTypes coreTypes, - ClassHierarchy hierarchy, bool nullSafety) { - final transformer = _Lowering(coreTypes, hierarchy, nullSafety); +void transformProcedure( + Procedure procedure, CoreTypes coreTypes, ClassHierarchy hierarchy, + {required bool nullSafety, required bool productMode}) { + final transformer = _Lowering(coreTypes, hierarchy, + nullSafety: nullSafety, productMode: productMode); procedure.accept(transformer); } @@ -38,15 +43,19 @@ class _Lowering extends Transformer { final LateVarInitTransformer lateVarInitTransformer; final FactorySpecializer factorySpecializer; final ListLiteralsLowering listLiteralsLowering; + final ForInLowering forInLowering; Member? _currentMember; + FunctionNode? _currentFunctionNode; StaticTypeContext? _cachedStaticTypeContext; - _Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, this.nullSafety) + _Lowering(CoreTypes coreTypes, ClassHierarchy hierarchy, + {required this.nullSafety, required bool productMode}) : env = TypeEnvironment(coreTypes, hierarchy), lateVarInitTransformer = LateVarInitTransformer(), factorySpecializer = FactorySpecializer(coreTypes), - listLiteralsLowering = ListLiteralsLowering(coreTypes); + listLiteralsLowering = ListLiteralsLowering(coreTypes), + forInLowering = ForInLowering(coreTypes, productMode: productMode); StaticTypeContext get _staticTypeContext => _cachedStaticTypeContext ??= StaticTypeContext(_currentMember!, env); @@ -69,6 +78,17 @@ class _Lowering extends Transformer { return result; } + @override + visitFunctionNode(FunctionNode node) { + final savedFunctionNode = _currentFunctionNode; + _currentFunctionNode = node; + + final result = super.visitFunctionNode(node); + + _currentFunctionNode = savedFunctionNode; + return result; + } + @override visitStaticInvocation(StaticInvocation node) { node.transformChildren(this); @@ -99,4 +119,11 @@ class _Lowering extends Transformer { node.transformChildren(this); return listLiteralsLowering.transformListLiteral(node); } + + @override + visitForInStatement(ForInStatement node) { + node.transformChildren(this); + return forInLowering.transformForInStatement( + node, _currentFunctionNode, _staticTypeContext); + } } diff --git a/runtime/docs/index.md b/runtime/docs/index.md index 8e261b04b4b..23a1a147305 100644 --- a/runtime/docs/index.md +++ b/runtime/docs/index.md @@ -98,7 +98,7 @@ Information about the class is fully deserialized only when runtime later needs At this point enough information is loaded from Kernel binary for runtime to successfully resolve and invoke methods. For example, it could resolve and invoke `main` function from a library. !!! sourcecode "Source to read" - @{package:kernel/ast.dart} defines classes describing the Kernel AST. @{package:front_end} handles parsing Dart source and building Kernel AST from it. @{dart::kernel::KernelLoader::LoadEntireProgram} is an entry point for deserialization of Kernel AST into corresponding VM objects. @{pkg/vm/bin/kernel_service.dart} implements the Kernel Service isolate, @{runtime/vm/kernel_isolate.cc} glues Dart implementation to the rest of the VM. @{package:vm} hosts most of the Kernel based VM specific functionality, e.g various Kernel-to-Kernel transformations. However some VM specific transformations still live in @{package:kernel} for historical reasons. A good example of a complicated transformation is @{package:kernel/transformations/continuation.dart}, which desugars `async`,`async*` and `sync*` functions. + @{package:kernel/ast.dart} defines classes describing the Kernel AST. @{package:front_end} handles parsing Dart source and building Kernel AST from it. @{dart::kernel::KernelLoader::LoadEntireProgram} is an entry point for deserialization of Kernel AST into corresponding VM objects. @{pkg/vm/bin/kernel_service.dart} implements the Kernel Service isolate, @{runtime/vm/kernel_isolate.cc} glues Dart implementation to the rest of the VM. @{package:vm} hosts most of the Kernel based VM specific functionality, e.g various Kernel-to-Kernel transformations. !!! tryit "Trying it" diff --git a/tests/lib/async/syncstar_mixed_iterators_test.dart b/tests/lib/async/syncstar_mixed_iterators_test.dart index 2b2ecf1441b..338c52b4a51 100644 --- a/tests/lib/async/syncstar_mixed_iterators_test.dart +++ b/tests/lib/async/syncstar_mixed_iterators_test.dart @@ -3,13 +3,13 @@ // BSD-style license that can be found in the LICENSE file. // // Test that sync* correctly iterates through nested iterators of both -// the internal sync* transform _SyncIterable and generic Iterable types. +// the internal sync* iterable and generic Iterable types. import "package:expect/expect.dart"; Iterable outerSyncStar() sync* { yield 1; - // _SyncIterable: + // internal sync* iterable: yield* innerSyncStar(); yield 4; // Generic Iterable: