mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 01:13:04 +00:00
[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 <vegorov@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
d5abb2a301
commit
94c120a6ea
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<dynamic>* :async_future = new asy::_Future::•<dynamic>();
|
||||
core::bool* :is_sync = false;
|
||||
FutureOr<dynamic>* :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<core::int*>* /* originally async */ {
|
||||
final asy::_Future<core::int*>* :async_future = new asy::_Future::•<core::int*>();
|
||||
core::bool* :is_sync = false;
|
||||
FutureOr<core::int*>* :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 {}
|
|
@ -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']}.');
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<int> 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<int> 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<void> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, String>? 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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Statement> statements = <Statement>[];
|
||||
|
||||
/// 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<VariableDeclaration> variables = <VariableDeclaration>[];
|
||||
|
||||
ExpressionLifter(this.continuationRewriter);
|
||||
|
||||
StatefulStaticTypeContext get _staticTypeContext =>
|
||||
continuationRewriter.staticTypeContext;
|
||||
|
||||
Block blockOf(List<Statement> 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<Statement> 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<Statement> 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(<Expression>[VariableGet(variable)],
|
||||
types: <DartType>[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 = <Statement>[];
|
||||
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 = <Statement>[];
|
||||
seenAwait = false;
|
||||
expr.then = delimit(() => transform(expr.then), thenStatements)
|
||||
..parent = expr;
|
||||
var thenAwait = seenAwait;
|
||||
|
||||
final thenNameIndex = nameIndex;
|
||||
nameIndex = savedNameIndex;
|
||||
|
||||
var otherwiseStatements = <Statement>[];
|
||||
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(<Expression>[
|
||||
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:
|
||||
//
|
||||
// <initializer's statements>
|
||||
// var x = <initializer's value>
|
||||
// <body's statements>
|
||||
//
|
||||
// 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<Statement> body = <Statement>[];
|
||||
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<Statement> savedInner = statements;
|
||||
List<Statement> savedOuter = continuationRewriter.statements;
|
||||
statements = <Statement>[];
|
||||
continuationRewriter.statements = <Statement>[];
|
||||
continuationRewriter.transform(stmt);
|
||||
|
||||
List<Statement> 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}");
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
222
pkg/vm/lib/transformations/for_in_lowering.dart
Normal file
222
pkg/vm/lib/transformations/for_in_lowering.dart
Normal file
|
@ -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 <stream-expression>) { ... }
|
||||
//
|
||||
// To (in product mode):
|
||||
//
|
||||
// {
|
||||
// :stream = <stream-expression>;
|
||||
// _StreamIterator<T> :for-iterator = new _StreamIterator<T>(:stream);
|
||||
// try {
|
||||
// while (await :for-iterator.moveNext()) {
|
||||
// T <variable> = :for-iterator.current;
|
||||
// ...
|
||||
// }
|
||||
// } finally {
|
||||
// if (:for-iterator._subscription != null)
|
||||
// await :for-iterator.cancel();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Or (in non-product mode):
|
||||
//
|
||||
// {
|
||||
// :stream = <stream-expression>;
|
||||
// _StreamIterator<T> :for-iterator = new _StreamIterator<T>(:stream);
|
||||
// try {
|
||||
// while (let _ = _asyncStarMoveNextHelper(:stream) in
|
||||
// await :for-iterator.moveNext()) {
|
||||
// T <variable> = :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(<Expression>[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 <variable> = :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(<Statement>[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(<Expression>[]),
|
||||
interfaceTarget: coreTypes.streamIteratorCancel,
|
||||
functionType:
|
||||
coreTypes.streamIteratorCancel.getterType as FunctionType))),
|
||||
null);
|
||||
|
||||
final tryFinally = new TryFinally(tryBody, tryFinalizer);
|
||||
|
||||
final block = new Block(
|
||||
<Statement>[streamVariable, forIteratorVariable, tryFinally]);
|
||||
return block;
|
||||
}
|
||||
|
||||
// Transform
|
||||
//
|
||||
// for ({var/final} T <variable> in <iterable>) { ... }
|
||||
//
|
||||
// Into
|
||||
//
|
||||
// {
|
||||
// final Iterator<T> :sync-for-iterator = <iterable>.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<E>`.
|
||||
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)]);
|
||||
}
|
||||
}
|
|
@ -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<Library> libraries, CoreTypes coreTypes,
|
||||
ClassHierarchy hierarchy, bool nullSafety) {
|
||||
final transformer = _Lowering(coreTypes, hierarchy, nullSafety);
|
||||
void transformLibraries(
|
||||
List<Library> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<int> outerSyncStar() sync* {
|
||||
yield 1;
|
||||
// _SyncIterable<int>:
|
||||
// internal sync* iterable:
|
||||
yield* innerSyncStar();
|
||||
yield 4;
|
||||
// Generic Iterable<int>:
|
||||
|
|
Loading…
Reference in a new issue