6b693e00bc
This adds a VariableDeclaration node which is used in ForStatement instead of VariableStatement. This is a step towards removing the initializer from Variable. Long term, VariableDeclaration will own the initializer expression for variables and function parameters will have a defaultValue property instead of using the initializer property for the default value. TEST=existing Change-Id: I4a663eeb6006a0f9f098fb2b3e3b502d2ae583b0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505681 Reviewed-by: Stephen Adams <sra@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com>
1537 lines
46 KiB
Dart
1537 lines
46 KiB
Dart
// 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/class_hierarchy.dart';
|
|
import 'package:kernel/clone.dart';
|
|
import 'package:kernel/core_types.dart';
|
|
import 'package:kernel/type_algebra.dart';
|
|
import 'package:kernel/type_environment.dart';
|
|
|
|
import 'factory_specializer.dart';
|
|
import 'util.dart';
|
|
|
|
void transformLibraries(
|
|
List<Library> libraries,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
) {
|
|
final transformer = _WasmTransformer(coreTypes, hierarchy);
|
|
libraries.forEach(transformer.visitLibrary);
|
|
}
|
|
|
|
void transformProcedure(
|
|
Procedure procedure,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
) {
|
|
final transformer = _WasmTransformer(coreTypes, hierarchy);
|
|
procedure.accept(transformer);
|
|
}
|
|
|
|
class _WasmTransformer extends Transformer {
|
|
final TypeEnvironment env;
|
|
|
|
Member? _currentMember;
|
|
StaticTypeContext? _cachedTypeContext;
|
|
final Set<Variable> _implicitFinalVariables = {};
|
|
|
|
final Library _coreLibrary;
|
|
final InterfaceType _nonNullableTypeType;
|
|
|
|
final Class _completerClass;
|
|
final Class _streamControllerClass;
|
|
final Class _wasmArrayClass;
|
|
final Class _wasmBaseClass;
|
|
|
|
final Procedure _completerComplete;
|
|
final Procedure _completerCompleteError;
|
|
final Procedure _completerConstructor;
|
|
final Procedure _completerSyncConstructor;
|
|
final Procedure _completerGetFuture;
|
|
final Procedure _streamControllerAdd;
|
|
final Procedure _streamControllerAddError;
|
|
final Procedure _streamControllerAddStream;
|
|
final Procedure _streamControllerClose;
|
|
final Procedure _streamControllerConstructor;
|
|
final Procedure _streamControllerGetHasListener;
|
|
final Procedure _streamControllerGetIsPaused;
|
|
final Procedure _streamControllerGetStream;
|
|
final Procedure _streamControllerSetOnListen;
|
|
|
|
final Procedure _trySetStackTraceForwarder;
|
|
final Procedure _trySetStackTrace;
|
|
|
|
final List<_AsyncStarFrame> _asyncStarFrames = [];
|
|
bool _enclosingIsAsyncStar = false;
|
|
|
|
final FactorySpecializer _factorySpecializer;
|
|
|
|
final PushPopWasmArrayTransformer _pushPopWasmArrayTransformer;
|
|
|
|
StaticTypeContext get typeContext =>
|
|
_cachedTypeContext ??= StaticTypeContext(_currentMember!, env);
|
|
|
|
CoreTypes get coreTypes => env.coreTypes;
|
|
|
|
/// Maps error handling function, constructor, factory references to functions
|
|
/// that, when minifying, throw errors without details, saving binary space.
|
|
///
|
|
/// Calls to these error handling function etc. references are transformed
|
|
/// when they are introduced by the front-end, as indicated by
|
|
/// [Throw.forErrorHandling].
|
|
late final Map<Reference, Procedure> _errorHandlingFunctions = {
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'fieldADI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorFieldADI',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'localADI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorLocalADI',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'fieldNI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorFieldNI',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'localNI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorLocalNI',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'fieldAI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorFieldAI',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'LateError', 'localAI')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwLateErrorLocalAI',
|
|
),
|
|
coreTypes.index
|
|
.getProcedure('dart:core', 'NoSuchMethodError', 'withInvocation')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwNoSuchMethodErrorWithInvocation',
|
|
),
|
|
coreTypes.index
|
|
.getConstructor('dart:_internal', 'ReachabilityError', '')
|
|
.reference: coreTypes.index.getTopLevelProcedure(
|
|
'dart:_error_utils',
|
|
'_throwReachabilityError',
|
|
),
|
|
};
|
|
|
|
_WasmTransformer(CoreTypes coreTypes, ClassHierarchy hierarchy)
|
|
: env = TypeEnvironment(coreTypes, hierarchy),
|
|
_nonNullableTypeType = coreTypes.index
|
|
.getClass('dart:core', '_Type')
|
|
.getThisType(coreTypes, Nullability.nonNullable),
|
|
_coreLibrary = coreTypes.index.getLibrary('dart:core'),
|
|
_completerClass = coreTypes.index.getClass('dart:async', 'Completer'),
|
|
_streamControllerClass = coreTypes.index.getClass(
|
|
'dart:async',
|
|
'StreamController',
|
|
),
|
|
_wasmArrayClass = coreTypes.index.getClass('dart:_wasm', 'WasmArray'),
|
|
_wasmBaseClass = coreTypes.index.getClass('dart:_wasm', '_WasmBase'),
|
|
_completerComplete = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'Completer',
|
|
'complete',
|
|
),
|
|
_completerCompleteError = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'Completer',
|
|
'completeError',
|
|
),
|
|
_completerConstructor = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'Completer',
|
|
'',
|
|
),
|
|
_completerSyncConstructor = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'Completer',
|
|
'sync',
|
|
),
|
|
_completerGetFuture = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'Completer',
|
|
'get:future',
|
|
),
|
|
_streamControllerAdd = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'add',
|
|
),
|
|
_streamControllerAddError = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'addError',
|
|
),
|
|
_streamControllerAddStream = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'addStream',
|
|
),
|
|
_streamControllerClose = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'close',
|
|
),
|
|
_streamControllerConstructor = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'',
|
|
),
|
|
_streamControllerGetHasListener = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'get:hasListener',
|
|
),
|
|
_streamControllerGetIsPaused = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'get:isPaused',
|
|
),
|
|
_streamControllerGetStream = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'get:stream',
|
|
),
|
|
_streamControllerSetOnListen = coreTypes.index.getProcedure(
|
|
'dart:async',
|
|
'StreamController',
|
|
'set:onListen',
|
|
),
|
|
_trySetStackTraceForwarder = coreTypes.index.getTopLevelProcedure(
|
|
'dart:async',
|
|
'_trySetStackTrace',
|
|
),
|
|
_trySetStackTrace = coreTypes.index.getProcedure(
|
|
'dart:core',
|
|
'Error',
|
|
'_trySetStackTrace',
|
|
),
|
|
_factorySpecializer = FactorySpecializer(coreTypes),
|
|
_pushPopWasmArrayTransformer = PushPopWasmArrayTransformer(coreTypes);
|
|
|
|
@override
|
|
defaultMember(Member node) {
|
|
_currentMember = node;
|
|
_cachedTypeContext = null;
|
|
_implicitFinalVariables.clear();
|
|
|
|
final result = super.defaultMember(node);
|
|
|
|
for (final node in _implicitFinalVariables) {
|
|
node.isFinal = true;
|
|
}
|
|
_currentMember = null;
|
|
_cachedTypeContext = null;
|
|
return result;
|
|
}
|
|
|
|
/// Checks to see if it is safe to reuse `super._typeArguments`.
|
|
bool canReuseSuperMethod(Class cls) {
|
|
// We search for the first non-abstract super in [cls]'s inheritance chain
|
|
// to see if we can reuse its `_typeArguments` method.
|
|
Class classIter = cls;
|
|
late Supertype supertype;
|
|
while (classIter.supertype != null) {
|
|
Supertype supertypeIter = classIter.supertype!;
|
|
Class superclass = supertypeIter.classNode;
|
|
if (!superclass.isAbstract) {
|
|
supertype = supertypeIter;
|
|
break;
|
|
}
|
|
classIter = classIter.supertype!.classNode;
|
|
}
|
|
|
|
// We can reuse a superclass' `_typeArguments` method if the subclass and
|
|
// the superclass have the exact same type parameters in the exact same
|
|
// order.
|
|
if (cls.typeParameters.length != supertype.typeArguments.length) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < cls.typeParameters.length; i++) {
|
|
TypeParameter parameter = cls.typeParameters[i];
|
|
DartType superTypeArg = supertype.typeArguments[i];
|
|
if (superTypeArg is! TypeParameterType ||
|
|
superTypeArg.parameter != parameter ||
|
|
superTypeArg.nullability == Nullability.nullable) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
defaultVariable(Variable node) {
|
|
if (!node.isFinal) {
|
|
_implicitFinalVariables.add(node);
|
|
}
|
|
return super.defaultVariable(node);
|
|
}
|
|
|
|
@override
|
|
visitVariableSet(VariableSet node) {
|
|
_implicitFinalVariables.remove(node.variable);
|
|
return super.visitVariableSet(node);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitClass(Class cls) {
|
|
// For every concrete class whose type parameters do not match the type
|
|
// parameters of it's super class we embed a special virtual function
|
|
// `_getTypeArguments`. When generating code for `_getTypeArguments`, we
|
|
// read the `TypeParameter`s off the instantiated object and generate a
|
|
// `List<Type>` to pass to `_getRuntimeType` which then returns a reified
|
|
// `Type` object.
|
|
if (!cls.isAbstract &&
|
|
cls != coreTypes.objectClass &&
|
|
!env.hierarchy.isSubclassOf(cls, _wasmBaseClass) &&
|
|
!canReuseSuperMethod(cls)) {
|
|
Procedure getTypeArguments = Procedure(
|
|
Name("_typeArguments", _coreLibrary),
|
|
ProcedureKind.Getter,
|
|
FunctionNode(
|
|
null,
|
|
returnType: InterfaceType(_wasmArrayClass, Nullability.nonNullable, [
|
|
_nonNullableTypeType,
|
|
]),
|
|
),
|
|
isExternal: true,
|
|
isSynthetic: true,
|
|
fileUri: cls.fileUri,
|
|
);
|
|
addPragma(getTypeArguments, 'wasm:intrinsic', coreTypes);
|
|
cls.addProcedure(getTypeArguments);
|
|
}
|
|
return super.visitClass(cls);
|
|
}
|
|
|
|
TreeNode _lowerForIn(ForInStatement stmt) {
|
|
// Transform
|
|
//
|
|
// for ({var/final} T <variable> in <iterable>) { ... }
|
|
//
|
|
// Into
|
|
//
|
|
// {
|
|
// final Iterator<T> #forIterator = <iterable>.iterator;
|
|
// for (; #forIterator.moveNext() ;) {
|
|
// {var/final} T variable = #forIterator.current;
|
|
// ...
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// and:
|
|
//
|
|
// await for ({var/final} T <variable> in <stream>) { ... }
|
|
//
|
|
// Into
|
|
//
|
|
// {
|
|
// final StreamIterator<T> #forIterator = StreamIterator(<stream>);
|
|
// bool #jumpSentinel = false;
|
|
// try {
|
|
// for (; jumpSentinel = await #forIterator.moveNext() ;) {
|
|
// {var/final} T variable = #forIterator.current;
|
|
// ...
|
|
// }
|
|
// } finally {
|
|
// if (#jumpSentinel) {
|
|
// await #forIterator.cancel();
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// 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(typeContext);
|
|
if (iterableType is InvalidType) {
|
|
return ExpressionStatement(
|
|
InvalidExpression('Invalid iterable type in for-in'),
|
|
);
|
|
}
|
|
|
|
final isAsync = stmt.isAsync;
|
|
late final Class iteratorClass;
|
|
late final Procedure iteratorMoveNext;
|
|
late final Member iteratorCurrent;
|
|
if (isAsync) {
|
|
iteratorClass = coreTypes.streamIteratorClass;
|
|
iteratorMoveNext = coreTypes.streamIteratorMoveNext;
|
|
iteratorCurrent = coreTypes.streamIteratorCurrent;
|
|
} else {
|
|
iteratorClass = coreTypes.iteratorClass;
|
|
iteratorMoveNext = coreTypes.iteratorMoveNext;
|
|
iteratorCurrent = coreTypes.iteratorGetCurrent;
|
|
}
|
|
|
|
final DartType elementType = stmt.getElementType(typeContext);
|
|
final iteratorType = InterfaceType(iteratorClass, Nullability.nonNullable, [
|
|
elementType,
|
|
]);
|
|
|
|
late final Expression iteratorInitializer;
|
|
if (isAsync) {
|
|
iteratorInitializer = ConstructorInvocation(
|
|
coreTypes.streamIteratorDefaultConstructor,
|
|
Arguments([iterable], types: [elementType]),
|
|
);
|
|
} else {
|
|
iteratorInitializer = InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
iterable,
|
|
Name('iterator'),
|
|
interfaceTarget: coreTypes.iterableGetIterator,
|
|
resultType: iteratorType,
|
|
);
|
|
}
|
|
|
|
final iterator = Variable(
|
|
"#forIterator",
|
|
initializer: iteratorInitializer..fileOffset = iterable.fileOffset,
|
|
type: iteratorType,
|
|
isSynthesized: true,
|
|
)..fileOffset = iterable.fileOffset;
|
|
|
|
// Only used when `isAsync` is true.
|
|
final jumpSentinel = Variable(
|
|
"#jumpSentinel",
|
|
initializer: ConstantExpression(BoolConstant(false)),
|
|
type: InterfaceType(coreTypes.boolClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
final condition = InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(iterator),
|
|
Name('moveNext'),
|
|
Arguments(const []),
|
|
interfaceTarget: iteratorMoveNext,
|
|
functionType: iteratorMoveNext.getterType as FunctionType,
|
|
)..fileOffset = iterable.fileOffset;
|
|
|
|
final variable = stmt.variable
|
|
..initializer = (InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(iterator),
|
|
Name('current'),
|
|
interfaceTarget: iteratorCurrent,
|
|
resultType: elementType,
|
|
)..fileOffset = stmt.bodyOffset);
|
|
|
|
Block body = Block([
|
|
VariableStatement(VariableDeclaration(variable)),
|
|
stmt.body,
|
|
])..fileOffset = stmt.fileOffset;
|
|
|
|
Statement forStatement = ForStatement(
|
|
const [],
|
|
isAsync
|
|
? VariableSet(jumpSentinel, AwaitExpression(condition))
|
|
: condition,
|
|
const [],
|
|
body,
|
|
);
|
|
|
|
// Wrap the body with a try / finally to cancel the stream on breaking out
|
|
// of the loop.
|
|
if (isAsync) {
|
|
forStatement = TryFinally(
|
|
Block([forStatement]),
|
|
Block([
|
|
IfStatement(
|
|
VariableGet(jumpSentinel),
|
|
ExpressionStatement(
|
|
AwaitExpression(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(iterator),
|
|
Name('cancel'),
|
|
Arguments(const []),
|
|
interfaceTarget: coreTypes.streamIteratorCancel,
|
|
functionType:
|
|
coreTypes.streamIteratorCancel.getterType as FunctionType,
|
|
),
|
|
),
|
|
),
|
|
null,
|
|
),
|
|
]),
|
|
);
|
|
}
|
|
|
|
return Block([
|
|
VariableStatement(VariableDeclaration(iterator)),
|
|
if (isAsync) VariableStatement(VariableDeclaration(jumpSentinel)),
|
|
forStatement,
|
|
]).accept<TreeNode>(this);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitForInStatement(ForInStatement stmt) {
|
|
return _lowerForIn(stmt);
|
|
}
|
|
|
|
InstanceInvocation _addToController(
|
|
Variable controller,
|
|
Expression expression,
|
|
int fileOffset,
|
|
) {
|
|
final controllerNullableObjectType = InterfaceType(
|
|
_streamControllerClass,
|
|
Nullability.nonNullable,
|
|
[coreTypes.objectNullableRawType],
|
|
);
|
|
FunctionType controllerAddType =
|
|
Substitution.fromInterfaceType(
|
|
controllerNullableObjectType,
|
|
).substituteType(
|
|
_streamControllerAdd.function.computeThisFunctionType(
|
|
Nullability.nonNullable,
|
|
),
|
|
)
|
|
as FunctionType;
|
|
return InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controller),
|
|
Name('add'),
|
|
Arguments([expression]),
|
|
interfaceTarget: _streamControllerAdd,
|
|
functionType: controllerAddType,
|
|
)..fileOffset = fileOffset;
|
|
}
|
|
|
|
TreeNode _lowerAsyncStar(FunctionNode functionNode) {
|
|
// Convert the function into:
|
|
//
|
|
// Stream<T> name(args) {
|
|
// var #paused;
|
|
// var #cancelCompleter;
|
|
// var #isDone = false;
|
|
// var #onCancelCallback = () {
|
|
// if (#isDone) return null;
|
|
// if (#paused != null) {
|
|
// #paused?.complete(null);
|
|
// #paused = null;
|
|
// }
|
|
// #cancelCompleter ??= Completer.sync<void>();
|
|
// return #cancelCompleter.future;
|
|
// };
|
|
// var #onResumeCallback = () {
|
|
// if (#paused != null) {
|
|
// #paused?.complete(null);
|
|
// #paused = null;
|
|
// }
|
|
// }
|
|
// var #controller = StreamController<T>(sync: true, onCancel: #onCancelCallback, onResume: #onResumeCallback);
|
|
//
|
|
// void #body() async {
|
|
// try {
|
|
// <transformed body>
|
|
// } catch (e, s) {
|
|
// if (#cancelCompleter != null) {
|
|
// #cancelCompleter?.completeError(e, s);
|
|
// #cancelCompleter = null;
|
|
// } else {
|
|
// #controller.addError(e, s);
|
|
// }
|
|
// } finally {
|
|
// #isDone = true;
|
|
// #controller.close();
|
|
// if (#cancelCompleter != null) {
|
|
// #cancelCompleter?.complete(null);
|
|
// #cancelCompleter = null;
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// #controller.onListen = () {
|
|
// scheduleMicrotask(#body);
|
|
// };
|
|
//
|
|
// return controller.stream;
|
|
// }
|
|
//
|
|
// Where `<transformed body>` is the body of `functionNode` with these
|
|
// transformations:
|
|
//
|
|
// - yield* e
|
|
//
|
|
// ==>
|
|
//
|
|
// await #controller.addStream(e);
|
|
// if (!#controller.hasListener) {
|
|
// return;
|
|
// }
|
|
//
|
|
// - yield e
|
|
//
|
|
// ==>
|
|
//
|
|
// #controller.add(e);
|
|
// if (#controller.isPaused) {
|
|
// await (#paused = Completer()).future;
|
|
// }
|
|
// if (!#controller.hasListener) {
|
|
// return;
|
|
// }
|
|
//
|
|
// The `yield` and `yield*` transformations are done by [visitYieldStatement].
|
|
|
|
final fileOffset = functionNode.fileOffset;
|
|
final emittedValueType = functionNode.emittedValueType!;
|
|
|
|
// var #controller = StreamController<T>(sync: true);
|
|
final controllerObjectType = InterfaceType(
|
|
_streamControllerClass,
|
|
Nullability.nonNullable,
|
|
[emittedValueType],
|
|
);
|
|
|
|
// `void #body() async { ... }` statements.
|
|
final List<Statement> bodyStatements = [];
|
|
|
|
// Completer<void>? #paused;
|
|
final pausedVarType = InterfaceType(_completerClass, Nullability.nullable, [
|
|
const VoidType(),
|
|
]);
|
|
|
|
final pausedVar = Variable(
|
|
'#paused',
|
|
initializer: null,
|
|
type: pausedVarType,
|
|
isSynthesized: true,
|
|
);
|
|
|
|
final cancelCompleterVar = Variable(
|
|
'#cancelCompleter',
|
|
initializer: null,
|
|
type: InterfaceType(_completerClass, Nullability.nullable, [
|
|
const VoidType(),
|
|
]),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
final isDoneVar = Variable(
|
|
'#isDone',
|
|
type: InterfaceType(coreTypes.boolClass, Nullability.nonNullable),
|
|
initializer: ConstantExpression(BoolConstant(false)),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
IfStatement makePauseCheck() => IfStatement(
|
|
EqualsNull(VariableGet(pausedVar)),
|
|
Block([]),
|
|
Block([
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(pausedVar),
|
|
Name('complete'),
|
|
Arguments([ConstantExpression(NullConstant())]),
|
|
interfaceTarget: _completerComplete,
|
|
functionType:
|
|
substitute(_completerComplete.getterType, {
|
|
_completerClass.typeParameters.first: const VoidType(),
|
|
})
|
|
as FunctionType,
|
|
),
|
|
),
|
|
ExpressionStatement(
|
|
VariableSet(pausedVar, ConstantExpression(NullConstant())),
|
|
),
|
|
]),
|
|
);
|
|
|
|
final List<Statement> onCancelCallbackBodyStatements = [
|
|
IfStatement(
|
|
VariableGet(isDoneVar),
|
|
ReturnStatement(ConstantExpression(NullConstant())),
|
|
null,
|
|
),
|
|
makePauseCheck(),
|
|
IfStatement(
|
|
EqualsNull(VariableGet(cancelCompleterVar)),
|
|
ExpressionStatement(
|
|
VariableSet(
|
|
cancelCompleterVar,
|
|
StaticInvocation(
|
|
_completerSyncConstructor,
|
|
Arguments([], types: [const VoidType()]),
|
|
),
|
|
),
|
|
),
|
|
null,
|
|
),
|
|
ReturnStatement(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(cancelCompleterVar),
|
|
Name('future'),
|
|
interfaceTarget: _completerGetFuture,
|
|
resultType: InterfaceType(
|
|
coreTypes.futureClass,
|
|
Nullability.nonNullable,
|
|
[const VoidType()],
|
|
),
|
|
),
|
|
),
|
|
];
|
|
|
|
final onCancelCallback = FunctionExpression(
|
|
FunctionNode(
|
|
Block(onCancelCallbackBodyStatements),
|
|
returnType: FutureOrType(const VoidType(), Nullability.nonNullable),
|
|
),
|
|
);
|
|
|
|
final onCancelCallbackVar = Variable(
|
|
"#onCancelCallback",
|
|
initializer: onCancelCallback,
|
|
);
|
|
|
|
final onResumeCallback = FunctionExpression(
|
|
FunctionNode(makePauseCheck(), returnType: const VoidType()),
|
|
);
|
|
|
|
final onResumeCallbackVar = Variable(
|
|
"#onResumeCallback",
|
|
initializer: onResumeCallback,
|
|
);
|
|
|
|
// StreamController<T>(sync: true)
|
|
final controllerInitializer = StaticInvocation(
|
|
_streamControllerConstructor,
|
|
Arguments(
|
|
[],
|
|
types: [emittedValueType],
|
|
named: [
|
|
NamedExpression('sync', ConstantExpression(BoolConstant(true))),
|
|
NamedExpression('onCancel', VariableGet(onCancelCallbackVar)),
|
|
NamedExpression('onResume', VariableGet(onResumeCallbackVar)),
|
|
],
|
|
),
|
|
);
|
|
|
|
// var #controller = ...
|
|
final controllerVar = Variable(
|
|
'#controller',
|
|
initializer: controllerInitializer..fileOffset = fileOffset,
|
|
type: controllerObjectType,
|
|
isSynthesized: true,
|
|
)..fileOffset = fileOffset;
|
|
|
|
_asyncStarFrames.add(
|
|
_AsyncStarFrame(controllerVar, pausedVar, emittedValueType),
|
|
);
|
|
final Statement transformedBody =
|
|
functionNode.body!.accept<TreeNode>(this) as Statement;
|
|
_asyncStarFrames.removeLast();
|
|
|
|
// The body will be wrapped with a `try-catch` to pass the error to the
|
|
// controller, and `try-finally` to close the controller.
|
|
final exceptionVar = Variable(null, isSynthesized: true);
|
|
|
|
final stackTraceVar = Variable(
|
|
null,
|
|
isSynthesized: true,
|
|
type: coreTypes.stackTraceRawType(Nullability.nonNullable),
|
|
);
|
|
|
|
final catch_ = Catch(
|
|
exceptionVar,
|
|
stackTrace: stackTraceVar,
|
|
IfStatement(
|
|
EqualsNull(VariableGet(cancelCompleterVar)),
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name("addError"),
|
|
Arguments([VariableGet(exceptionVar), VariableGet(stackTraceVar)]),
|
|
interfaceTarget: _streamControllerAddError,
|
|
functionType: _streamControllerAddError.getterType as FunctionType,
|
|
),
|
|
),
|
|
Block([
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(cancelCompleterVar),
|
|
Name("completeError"),
|
|
Arguments([
|
|
VariableGet(exceptionVar),
|
|
VariableGet(stackTraceVar),
|
|
]),
|
|
interfaceTarget: _completerCompleteError,
|
|
functionType: _completerCompleteError.getterType as FunctionType,
|
|
),
|
|
),
|
|
ExpressionStatement(
|
|
VariableSet(cancelCompleterVar, ConstantExpression(NullConstant())),
|
|
),
|
|
]),
|
|
),
|
|
);
|
|
|
|
final finalizer = Block([
|
|
ExpressionStatement(
|
|
VariableSet(isDoneVar, ConstantExpression(BoolConstant(true))),
|
|
),
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name("close"),
|
|
Arguments([]),
|
|
interfaceTarget: _streamControllerClose,
|
|
functionType: _streamControllerClose.getterType as FunctionType,
|
|
),
|
|
),
|
|
// Only complete the cancel completer if there was no error.
|
|
IfStatement(
|
|
Not(EqualsNull(VariableGet(cancelCompleterVar))),
|
|
ExpressionStatement(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(cancelCompleterVar),
|
|
Name('complete'),
|
|
Arguments([ConstantExpression(NullConstant())]),
|
|
interfaceTarget: _completerComplete,
|
|
functionType:
|
|
substitute(_completerComplete.getterType, {
|
|
_completerClass.typeParameters.first: const VoidType(),
|
|
})
|
|
as FunctionType,
|
|
),
|
|
),
|
|
null,
|
|
),
|
|
]);
|
|
|
|
bodyStatements.add(
|
|
TryFinally(TryCatch(transformedBody, [catch_]), finalizer),
|
|
);
|
|
|
|
final bodyFunction = FunctionNode(
|
|
Block(bodyStatements),
|
|
emittedValueType: const VoidType(),
|
|
returnType: InterfaceType(
|
|
coreTypes.futureClass,
|
|
Nullability.nonNullable,
|
|
[const VoidType()],
|
|
),
|
|
asyncMarker: AsyncMarker.Async,
|
|
dartAsyncMarker: AsyncMarker.Async,
|
|
);
|
|
|
|
final bodyInitializer = FunctionExpression(bodyFunction);
|
|
|
|
final bodyFunctionType = bodyFunction.computeThisFunctionType(
|
|
Nullability.nonNullable,
|
|
);
|
|
|
|
final bodyVar = Variable(
|
|
'#body',
|
|
initializer: bodyInitializer..fileOffset = fileOffset,
|
|
type: bodyFunctionType,
|
|
isSynthesized: true,
|
|
)..fileOffset = fileOffset;
|
|
|
|
// controller.onListen = () {
|
|
// scheduleMicrotask(_body);
|
|
// };
|
|
final scheduleMicrotaskProcedure = coreTypes.index.getTopLevelProcedure(
|
|
'dart:async',
|
|
'scheduleMicrotask',
|
|
);
|
|
|
|
final setControllerOnListen = InstanceSet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name('onListen'),
|
|
FunctionExpression(
|
|
FunctionNode(
|
|
ExpressionStatement(
|
|
StaticInvocation(
|
|
scheduleMicrotaskProcedure,
|
|
Arguments([VariableGet(bodyVar)]),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
interfaceTarget: _streamControllerSetOnListen,
|
|
);
|
|
|
|
return FunctionNode(
|
|
Block([
|
|
VariableStatement(VariableDeclaration(pausedVar)),
|
|
VariableStatement(VariableDeclaration(cancelCompleterVar)),
|
|
VariableStatement(VariableDeclaration(isDoneVar)),
|
|
VariableStatement(VariableDeclaration(onCancelCallbackVar)),
|
|
VariableStatement(VariableDeclaration(onResumeCallbackVar)),
|
|
|
|
// var controller = StreamController<T>(sync: true, onCancel: onCancelCallback, onResume: onResumeCallback);
|
|
VariableStatement(VariableDeclaration(controllerVar)),
|
|
|
|
// var #body = ...;
|
|
VariableStatement(VariableDeclaration(bodyVar)),
|
|
|
|
// controller.onListen = ...;
|
|
ExpressionStatement(setControllerOnListen),
|
|
|
|
// return controller.stream;
|
|
ReturnStatement(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name("stream"),
|
|
interfaceTarget: _streamControllerGetStream,
|
|
resultType: substitute(_streamControllerGetStream.getterType, {
|
|
_streamControllerClass.typeParameters.first: emittedValueType,
|
|
}),
|
|
),
|
|
),
|
|
]),
|
|
typeParameters: functionNode.typeParameters,
|
|
positionalParameters: functionNode.positionalParameters,
|
|
namedParameters: functionNode.namedParameters,
|
|
requiredParameterCount: functionNode.requiredParameterCount,
|
|
returnType: functionNode.returnType,
|
|
asyncMarker: AsyncMarker.Sync,
|
|
dartAsyncMarker: AsyncMarker.Sync,
|
|
);
|
|
}
|
|
|
|
void _lowerAsync(FunctionNode functionNode) {
|
|
/*
|
|
Convert `async` functions with "simple" bodies to `sync` functions, using
|
|
`Future.value`.
|
|
|
|
"Simple" means: constant or basic literal. In general, this transformation
|
|
can be done on any function body that doesn't `await` and doesn't throw.
|
|
|
|
Example:
|
|
|
|
foo() async { return const ...; }
|
|
==>
|
|
foo() { return Future.value(const ...); }
|
|
*/
|
|
final functionBody = functionNode.body!;
|
|
final simpleReturn = _getSimpleReturn(functionBody);
|
|
if (simpleReturn is BasicLiteral || simpleReturn is ConstantExpression) {
|
|
final futureValueType = functionNode.emittedValueType!;
|
|
final newBody = ReturnStatement(
|
|
StaticInvocation(
|
|
coreTypes.futureValueFactory,
|
|
Arguments([simpleReturn!], types: [futureValueType]),
|
|
),
|
|
);
|
|
newBody.parent = functionNode;
|
|
functionNode.body = newBody;
|
|
functionNode.asyncMarker = AsyncMarker.Sync;
|
|
}
|
|
}
|
|
|
|
@override
|
|
TreeNode visitYieldStatement(YieldStatement yield) {
|
|
// We currently ignore yields in 'sync*'.
|
|
if (!_enclosingIsAsyncStar) {
|
|
return super.visitYieldStatement(yield);
|
|
}
|
|
|
|
final fileOffset = yield.fileOffset;
|
|
final frame = _asyncStarFrames.last;
|
|
final controllerVar = frame.controllerVar;
|
|
final pausedVar = frame.pausedVar;
|
|
final isYieldStar = yield.isYieldStar;
|
|
|
|
final transformedExpression = yield.expression.accept(this) as Expression;
|
|
|
|
final List<Statement> statements = [];
|
|
|
|
if (isYieldStar) {
|
|
// yield* e
|
|
//
|
|
// ==>
|
|
//
|
|
// await #controller.addStream(e);
|
|
// if (!#controller.hasListener) return;
|
|
|
|
final controllerAddStreamProcedureType =
|
|
_streamControllerAddStream.getterType as FunctionType;
|
|
|
|
statements.add(
|
|
ExpressionStatement(
|
|
AwaitExpression(
|
|
InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name('addStream'),
|
|
Arguments([transformedExpression]),
|
|
interfaceTarget: _streamControllerAddStream,
|
|
functionType:
|
|
substitute(controllerAddStreamProcedureType, {
|
|
_streamControllerClass.typeParameters.first:
|
|
frame.emittedValueType,
|
|
})
|
|
as FunctionType,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
statements.add(
|
|
IfStatement(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name('hasListener'),
|
|
interfaceTarget: _streamControllerGetHasListener,
|
|
resultType: coreTypes.boolNonNullableRawType,
|
|
),
|
|
Block([]),
|
|
ReturnStatement(),
|
|
),
|
|
);
|
|
} else {
|
|
// yield e
|
|
//
|
|
// ==>
|
|
//
|
|
// #controller.add(e);
|
|
// if (#controller.isPaused) {
|
|
// await (#paused = Completer()).future;
|
|
// }
|
|
// if (!#controller.hasListener) {
|
|
// return;
|
|
// }
|
|
|
|
statements.add(
|
|
ExpressionStatement(
|
|
_addToController(controllerVar, yield.expression, fileOffset),
|
|
),
|
|
);
|
|
|
|
// if (controller.isPaused) ...
|
|
statements.add(
|
|
IfStatement(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name('isPaused'),
|
|
interfaceTarget: _streamControllerGetIsPaused,
|
|
resultType: coreTypes.boolNonNullableRawType,
|
|
),
|
|
ExpressionStatement(
|
|
AwaitExpression(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableSet(
|
|
pausedVar,
|
|
StaticInvocation(
|
|
_completerConstructor,
|
|
Arguments([], types: [const VoidType()]),
|
|
),
|
|
),
|
|
Name('future'),
|
|
interfaceTarget: _completerGetFuture,
|
|
resultType: substitute(_completerGetFuture.getterType, {
|
|
_completerClass.typeParameters.first: const VoidType(),
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
null,
|
|
),
|
|
);
|
|
|
|
// if (!controller.hasListener) return;
|
|
statements.add(
|
|
IfStatement(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(controllerVar),
|
|
Name('hasListener'),
|
|
interfaceTarget: _streamControllerGetHasListener,
|
|
resultType: coreTypes.boolNonNullableRawType,
|
|
),
|
|
Block([]),
|
|
ReturnStatement(),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Block(statements);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionNode(FunctionNode functionNode) {
|
|
final previousEnclosing = _enclosingIsAsyncStar;
|
|
final FunctionNode transformed;
|
|
|
|
if (functionNode.asyncMarker == AsyncMarker.AsyncStar) {
|
|
_enclosingIsAsyncStar = true;
|
|
functionNode = _lowerAsyncStar(functionNode) as FunctionNode;
|
|
_enclosingIsAsyncStar = previousEnclosing;
|
|
transformed = super.visitFunctionNode(functionNode) as FunctionNode;
|
|
} else {
|
|
_enclosingIsAsyncStar = false;
|
|
transformed = super.visitFunctionNode(functionNode) as FunctionNode;
|
|
_enclosingIsAsyncStar = previousEnclosing;
|
|
}
|
|
|
|
if (transformed.asyncMarker == AsyncMarker.Async) {
|
|
_lowerAsync(transformed);
|
|
}
|
|
|
|
return transformed;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitStaticInvocation(StaticInvocation node) {
|
|
node.transformChildren(this);
|
|
|
|
// Forward calls in `dart:async` to private `dart:core` method.
|
|
if (node.target == _trySetStackTraceForwarder) {
|
|
node.target = _trySetStackTrace;
|
|
}
|
|
|
|
TreeNode transformed = _pushPopWasmArrayTransformer
|
|
.transformStaticInvocation(node);
|
|
|
|
if (transformed is StaticInvocation) {
|
|
transformed = _factorySpecializer.transformStaticInvocation(transformed);
|
|
}
|
|
|
|
return transformed;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionTearOff(FunctionTearOff node) {
|
|
node.transformChildren(this);
|
|
return node.receiver;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitThrow(Throw node) {
|
|
node.transformChildren(this);
|
|
if (node.forErrorHandling) {
|
|
final expression = node.expression;
|
|
if (expression is ConstructorInvocation) {
|
|
final throwFunction =
|
|
_errorHandlingFunctions[expression.targetReference];
|
|
if (throwFunction != null) {
|
|
return StaticInvocation(throwFunction, expression.arguments);
|
|
}
|
|
} else if (expression is StaticInvocation) {
|
|
final throwFunction =
|
|
_errorHandlingFunctions[expression.targetReference];
|
|
if (throwFunction != null) {
|
|
return StaticInvocation(throwFunction, expression.arguments);
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
}
|
|
|
|
class _AsyncStarFrame {
|
|
final Variable controllerVar;
|
|
final Variable pausedVar;
|
|
final DartType emittedValueType;
|
|
|
|
_AsyncStarFrame(this.controllerVar, this.pausedVar, this.emittedValueType);
|
|
}
|
|
|
|
/// Converts `pushWasmArray<T>(array, length, elem, nextCapacity)` to:
|
|
///
|
|
/// if (array.length == length) {
|
|
/// final newArray = WasmArray<T>(nextCapacity);
|
|
/// newArray.copy(0, array, 0, length);
|
|
/// array = newArray;
|
|
/// }
|
|
/// array[length] = elem;
|
|
/// length += 1;
|
|
///
|
|
/// and `popWasmArray<T>(array, length)` to block expression:
|
|
///
|
|
/// {
|
|
/// length -= 1;
|
|
/// final T _value = array[length];
|
|
/// array[length] = null;
|
|
/// } => _value
|
|
///
|
|
/// This allows unboxing growable list in class fields.
|
|
///
|
|
/// `array` and `length` arguments need to be either `VariableGet` or
|
|
/// `InstanceGet`.
|
|
class PushPopWasmArrayTransformer {
|
|
final CoreTypes _coreTypes;
|
|
final Procedure _intAdd;
|
|
final Procedure _intSubtract;
|
|
final InterfaceType _intType;
|
|
final Procedure _popWasmArray;
|
|
final Procedure _pushWasmArray;
|
|
final Class _wasmArrayClass;
|
|
final Procedure _wasmArrayCopy;
|
|
final Procedure _wasmArrayElementGet;
|
|
final Procedure _wasmArrayElementSet;
|
|
final Procedure _wasmArrayFactory;
|
|
final Member _wasmArrayLength;
|
|
|
|
PushPopWasmArrayTransformer(this._coreTypes)
|
|
: _intAdd = _coreTypes.index.getProcedure('dart:core', 'num', '+'),
|
|
_intSubtract = _coreTypes.index.getProcedure('dart:core', 'num', '-'),
|
|
_intType = _coreTypes.intNonNullableRawType,
|
|
_popWasmArray = _coreTypes.index.getTopLevelProcedure(
|
|
'dart:_internal',
|
|
'popWasmArray',
|
|
),
|
|
_pushWasmArray = _coreTypes.index.getTopLevelProcedure(
|
|
'dart:_internal',
|
|
'pushWasmArray',
|
|
),
|
|
_wasmArrayClass = _coreTypes.index.getClass('dart:_wasm', 'WasmArray'),
|
|
_wasmArrayCopy = _coreTypes.index.getProcedure(
|
|
'dart:_wasm',
|
|
'WasmArrayExt',
|
|
'copy',
|
|
),
|
|
_wasmArrayElementGet = _coreTypes.index.getProcedure(
|
|
'dart:_wasm',
|
|
'WasmArrayExt',
|
|
'[]',
|
|
),
|
|
_wasmArrayElementSet = _coreTypes.index.getProcedure(
|
|
'dart:_wasm',
|
|
'WasmArrayExt',
|
|
'[]=',
|
|
),
|
|
_wasmArrayFactory = _coreTypes.index.getProcedure(
|
|
'dart:_wasm',
|
|
'WasmArray',
|
|
'',
|
|
),
|
|
_wasmArrayLength = _coreTypes.index.getProcedure(
|
|
'dart:_wasm',
|
|
'WasmArrayRef',
|
|
'get:length',
|
|
);
|
|
|
|
Expression transformStaticInvocation(StaticInvocation invocation) {
|
|
if (invocation.target == _pushWasmArray) {
|
|
return _transformPushWasmArray(invocation);
|
|
} else if (invocation.target == _popWasmArray) {
|
|
return _transformPopWasmArray(invocation);
|
|
} else {
|
|
return invocation;
|
|
}
|
|
}
|
|
|
|
Expression _transformPushWasmArray(StaticInvocation invocation) {
|
|
final elementType = invocation.arguments.types[0];
|
|
|
|
final positionalArguments = invocation.arguments.positional;
|
|
assert(positionalArguments.length == 4);
|
|
|
|
final array = positionalArguments[0];
|
|
final length = positionalArguments[1];
|
|
final elem = positionalArguments[2];
|
|
final nextCapacity = positionalArguments[3];
|
|
|
|
assert(array is InstanceGet || array is VariableGet);
|
|
assert(length is InstanceGet || length is VariableGet);
|
|
|
|
// Collect variables referenced in `VariableGet`s. These will be passed to
|
|
// the cloner as "already cloned" to avoid cloning them.
|
|
final variableCollector = _VariableCollector();
|
|
array.accept(variableCollector);
|
|
length.accept(variableCollector);
|
|
elem.accept(variableCollector);
|
|
nextCapacity.accept(variableCollector);
|
|
|
|
final variables = variableCollector.variables;
|
|
|
|
// Clone an expression.
|
|
Expression clone(Expression node) {
|
|
final cloner = CloneVisitorNotMembers();
|
|
for (final variable in variables) {
|
|
cloner.setVariableClone(variable, variable);
|
|
}
|
|
return cloner.clone(node);
|
|
}
|
|
|
|
// array.length == length
|
|
final objectEqualsType = _coreTypes.objectEquals
|
|
.computeSignatureOrFunctionType();
|
|
final lengthCheck = EqualsCall(
|
|
InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
array,
|
|
Name('length'),
|
|
interfaceTarget: _wasmArrayLength,
|
|
resultType: _intType,
|
|
),
|
|
length,
|
|
functionType: objectEqualsType,
|
|
interfaceTarget: _coreTypes.objectEquals,
|
|
);
|
|
|
|
// WasmArray<T>(nextCapacity)
|
|
final arrayAllocation = StaticInvocation(
|
|
_wasmArrayFactory,
|
|
Arguments([nextCapacity], types: [elementType]),
|
|
);
|
|
|
|
// var newArray = WasmArray<T>(nextCapacity)
|
|
final newArrayVariable = Variable(
|
|
'newArray',
|
|
initializer: arrayAllocation,
|
|
type: InterfaceType(_wasmArrayClass, Nullability.nonNullable, [
|
|
elementType,
|
|
]),
|
|
);
|
|
|
|
// newArray.copy(...)
|
|
final newArrayCopy = StaticInvocation(
|
|
_wasmArrayCopy,
|
|
Arguments(
|
|
[
|
|
VariableGet(newArrayVariable),
|
|
IntLiteral(0),
|
|
clone(array),
|
|
IntLiteral(0),
|
|
clone(length),
|
|
],
|
|
types: [elementType],
|
|
),
|
|
);
|
|
|
|
// array = newArray
|
|
final Statement arrayFieldUpdate;
|
|
if (array is InstanceGet) {
|
|
arrayFieldUpdate = ExpressionStatement(
|
|
InstanceSet(
|
|
array.kind,
|
|
clone(array.receiver),
|
|
array.name,
|
|
VariableGet(newArrayVariable),
|
|
interfaceTarget: array.interfaceTarget,
|
|
),
|
|
);
|
|
} else {
|
|
final arrayVariableGet = array as VariableGet;
|
|
arrayFieldUpdate = ExpressionStatement(
|
|
VariableSet(arrayVariableGet.variable, VariableGet(newArrayVariable)),
|
|
);
|
|
}
|
|
|
|
final List<Statement> arrayGrowStatements = [
|
|
VariableStatement(VariableDeclaration(newArrayVariable)),
|
|
ExpressionStatement(newArrayCopy),
|
|
arrayFieldUpdate,
|
|
];
|
|
|
|
// array[length] = elem
|
|
final arrayPush = ExpressionStatement(
|
|
StaticInvocation(
|
|
_wasmArrayElementSet,
|
|
Arguments([clone(array), clone(length), elem], types: [elementType]),
|
|
),
|
|
);
|
|
|
|
// length + 1
|
|
final intAddType = _intAdd.computeSignatureOrFunctionType();
|
|
final lengthPlusOne = InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
clone(length),
|
|
Name('+'),
|
|
Arguments([IntLiteral(1)]),
|
|
interfaceTarget: _intAdd,
|
|
functionType: intAddType,
|
|
);
|
|
|
|
// length = length + 1
|
|
final Statement arrayLengthUpdate;
|
|
if (length is InstanceGet) {
|
|
arrayLengthUpdate = ExpressionStatement(
|
|
InstanceSet(
|
|
length.kind,
|
|
clone(length.receiver),
|
|
length.name,
|
|
lengthPlusOne,
|
|
interfaceTarget: length.interfaceTarget,
|
|
),
|
|
);
|
|
} else {
|
|
final lengthVariableGet = length as VariableGet;
|
|
arrayLengthUpdate = ExpressionStatement(
|
|
VariableSet(lengthVariableGet.variable, lengthPlusOne),
|
|
);
|
|
}
|
|
|
|
return BlockExpression(
|
|
Block([
|
|
IfStatement(lengthCheck, Block(arrayGrowStatements), null),
|
|
arrayPush,
|
|
arrayLengthUpdate,
|
|
]),
|
|
NullLiteral(),
|
|
);
|
|
}
|
|
|
|
Expression _transformPopWasmArray(StaticInvocation invocation) {
|
|
final elementType = invocation.arguments.types[0] as InterfaceType;
|
|
final elementIsNullable =
|
|
elementType.nullability != Nullability.nonNullable;
|
|
|
|
final positionalArguments = invocation.arguments.positional;
|
|
assert(positionalArguments.length == 2);
|
|
|
|
final array = positionalArguments[0];
|
|
final length = positionalArguments[1];
|
|
|
|
assert(array is InstanceGet || array is VariableGet);
|
|
assert(length is InstanceGet || length is VariableGet);
|
|
|
|
// Collect variables referenced in `VariableGet`s. These will be passed to
|
|
// the cloner as "already cloned" to avoid cloning them.
|
|
final variableCollector = _VariableCollector();
|
|
array.accept(variableCollector);
|
|
length.accept(variableCollector);
|
|
|
|
final variables = variableCollector.variables;
|
|
|
|
// Clone an expression.
|
|
Expression clone(Expression node) {
|
|
final cloner = CloneVisitorNotMembers();
|
|
for (final variable in variables) {
|
|
cloner.setVariableClone(variable, variable);
|
|
}
|
|
return cloner.clone(node);
|
|
}
|
|
|
|
final List<Statement> blockStatements = [];
|
|
|
|
// length - 1
|
|
final intSubtractType = _intSubtract.computeSignatureOrFunctionType();
|
|
final lengthMinusOne = InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
clone(length),
|
|
Name('-'),
|
|
Arguments([IntLiteral(1)]),
|
|
interfaceTarget: _intSubtract,
|
|
functionType: intSubtractType,
|
|
);
|
|
|
|
// length -= 1
|
|
final Statement arrayLengthUpdate;
|
|
if (length is InstanceGet) {
|
|
arrayLengthUpdate = ExpressionStatement(
|
|
InstanceSet(
|
|
length.kind,
|
|
clone(length.receiver),
|
|
length.name,
|
|
lengthMinusOne,
|
|
interfaceTarget: length.interfaceTarget,
|
|
),
|
|
);
|
|
} else {
|
|
final lengthVariableGet = length as VariableGet;
|
|
arrayLengthUpdate = ExpressionStatement(
|
|
VariableSet(lengthVariableGet.variable, lengthMinusOne),
|
|
);
|
|
}
|
|
blockStatements.add(arrayLengthUpdate);
|
|
|
|
// array[length]
|
|
final arrayGet = StaticInvocation(
|
|
_wasmArrayElementGet,
|
|
Arguments([clone(array), clone(length)], types: [elementType]),
|
|
);
|
|
|
|
// final temp = array[length]
|
|
final arrayGetVariable = Variable.forValue(
|
|
arrayGet,
|
|
isFinal: true,
|
|
type: elementType,
|
|
);
|
|
blockStatements.add(
|
|
VariableStatement(VariableDeclaration(arrayGetVariable)),
|
|
);
|
|
|
|
// array[length] = null
|
|
if (elementIsNullable) {
|
|
final arrayClearElement = ExpressionStatement(
|
|
StaticInvocation(
|
|
_wasmArrayElementSet,
|
|
Arguments(
|
|
[clone(array), clone(length), NullLiteral()],
|
|
types: [elementType],
|
|
),
|
|
),
|
|
);
|
|
blockStatements.add(arrayClearElement);
|
|
}
|
|
|
|
return BlockExpression(
|
|
Block(blockStatements),
|
|
VariableGet(arrayGetVariable),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _VariableCollector extends RecursiveVisitor {
|
|
Set<Variable> variables = {};
|
|
|
|
@override
|
|
void visitVariableGet(VariableGet node) {
|
|
variables.add(node.variable);
|
|
}
|
|
}
|
|
|
|
Expression? _getSimpleReturn(Statement functionBody) {
|
|
if (functionBody is ReturnStatement) {
|
|
if (functionBody.expression == null) {
|
|
return NullLiteral();
|
|
}
|
|
return functionBody.expression;
|
|
}
|
|
|
|
if (functionBody is Block) {
|
|
if (functionBody.statements.isEmpty) {
|
|
return NullLiteral();
|
|
}
|
|
if (functionBody.statements.length == 1) {
|
|
final statement = functionBody.statements.single;
|
|
if (statement is ReturnStatement) {
|
|
return statement.expression;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|