blob: 0c54b2c2f01298416d17bb1120bb4224adad5e49 [file] [log] [blame]
// 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/core_types.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:vm/modular/transformations/type_casts_optimizer.dart'
as typeCastsOptimizer show transformAsExpression;
import 'list_factory_specializer.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<VariableDeclaration> _implicitFinalVariables = {};
final Library _coreLibrary;
final InterfaceType _nonNullableTypeType;
final Class _completerClass;
final Class _streamControllerClass;
final Class _wasmArrayClass;
final Class _wasmBaseClass;
final Procedure _completerComplete;
final Procedure _completerConstructor;
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 _streamControllerSetOnCancel;
final Procedure _streamControllerSetOnListen;
final Procedure _streamControllerSetOnResume;
final List<_AsyncStarFrame> _asyncStarFrames = [];
bool _enclosingIsAsyncStar = false;
final ListFactorySpecializer _listFactorySpecializer;
StaticTypeContext get typeContext =>
_cachedTypeContext ??= StaticTypeContext(_currentMember!, env);
CoreTypes get coreTypes => env.coreTypes;
_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'),
_completerConstructor =
coreTypes.index.getProcedure('dart:async', 'Completer', ''),
_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'),
_streamControllerSetOnCancel = coreTypes.index
.getProcedure('dart:async', 'StreamController', 'set:onCancel'),
_streamControllerSetOnListen = coreTypes.index
.getProcedure('dart:async', 'StreamController', 'set:onListen'),
_streamControllerSetOnResume = coreTypes.index
.getProcedure('dart:async', 'StreamController', 'set:onResume'),
_listFactorySpecializer = ListFactorySpecializer(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
visitVariableDeclaration(VariableDeclaration node) {
if (!node.isFinal) {
_implicitFinalVariables.add(node);
}
return super.visitVariableDeclaration(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);
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 = VariableDeclaration("#forIterator",
initializer: iteratorInitializer..fileOffset = iterable.fileOffset,
type: iteratorType,
isSynthesized: true)
..fileOffset = iterable.fileOffset;
// Only used when `isAsync` is true.
final jumpSentinel = VariableDeclaration("#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([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([iterator, if (isAsync) jumpSentinel, forStatement])
.accept<TreeNode>(this);
}
@override
TreeNode visitForInStatement(ForInStatement stmt) {
return _lowerForIn(stmt);
}
InstanceInvocation _addToController(
VariableDeclaration 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 #controller = StreamController<T>(sync: true);
//
// void #body() async {
// Completer<void>? #paused;
//
// #controller.onResume = #controller.onCancel = () {
// #paused?.complete(null);
// #paused = null;
// };
//
// try {
// <transformed body>
// } catch (e, s) {
// #controller.addError(e, s);
// } finally {
// #controller.close();
// }
// }
//
// #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]);
// StreamController<T>(sync: true)
final controllerInitializer = StaticInvocation(
_streamControllerConstructor,
Arguments([], types: [
emittedValueType
], named: [
NamedExpression('sync', ConstantExpression(BoolConstant(true)))
]));
// var #controller = ...
final controllerVar = VariableDeclaration('#controller',
initializer: controllerInitializer..fileOffset = fileOffset,
type: controllerObjectType,
isSynthesized: true)
..fileOffset = fileOffset;
// `void #body() async { ... }` statements.
final List<Statement> bodyStatements = [];
// Completer<void>? #paused;
final pausedVarType = InterfaceType(
_completerClass, Nullability.nullable, [const VoidType()]);
final pausedVar = VariableDeclaration('#paused',
initializer: null, type: pausedVarType, isSynthesized: true);
bodyStatements.add(pausedVar);
// controller.onResume = controller.onCancel = () {
// #paused?.complete(null);
// #paused = null;
// };
final List<Statement> onCancelCallbackBodyStatements = [
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 onCancelCallback = FunctionExpression(FunctionNode(
Block(onCancelCallbackBodyStatements),
typeParameters: [],
positionalParameters: [],
namedParameters: [],
requiredParameterCount: 0,
returnType: const VoidType(),
));
final onCancelCallbackVar =
VariableDeclaration("#onCancelCallback", initializer: onCancelCallback);
bodyStatements.add(onCancelCallbackVar);
bodyStatements.add(ExpressionStatement(InstanceSet(
InstanceAccessKind.Instance,
VariableGet(controllerVar),
Name('onResume'),
VariableGet(onCancelCallbackVar),
interfaceTarget: _streamControllerSetOnResume)));
bodyStatements.add(ExpressionStatement(InstanceSet(
InstanceAccessKind.Instance,
VariableGet(controllerVar),
Name('onCancel'),
VariableGet(onCancelCallbackVar),
interfaceTarget: _streamControllerSetOnCancel)));
_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 = VariableDeclaration(null, isSynthesized: true);
final stackTraceVar = VariableDeclaration(null,
isSynthesized: true,
type: coreTypes.stackTraceRawType(Nullability.nonNullable));
final catch_ = Catch(
exceptionVar,
stackTrace: stackTraceVar,
ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(controllerVar),
Name("addError"),
Arguments([VariableGet(exceptionVar), VariableGet(stackTraceVar)]),
interfaceTarget: _streamControllerAddError,
functionType: _streamControllerAddError.getterType as FunctionType,
)));
final finalizer = ExpressionStatement(InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(controllerVar),
Name("close"),
Arguments([]),
interfaceTarget: _streamControllerClose,
functionType: _streamControllerClose.getterType as FunctionType,
));
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 = VariableDeclaration('#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([
// var controller = StreamController<T>(sync: true);
controllerVar,
// var #body = ...;
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);
}
@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;
if (functionNode.dartAsyncMarker == AsyncMarker.AsyncStar) {
_enclosingIsAsyncStar = true;
functionNode = _lowerAsyncStar(functionNode) as FunctionNode;
_enclosingIsAsyncStar = previousEnclosing;
return super.visitFunctionNode(functionNode);
} else {
_enclosingIsAsyncStar = false;
TreeNode result = super.visitFunctionNode(functionNode);
_enclosingIsAsyncStar = previousEnclosing;
return result;
}
}
@override
TreeNode visitStaticInvocation(StaticInvocation node) {
node.transformChildren(this);
return _listFactorySpecializer.transformStaticInvocation(node);
}
@override
visitFunctionTearOff(FunctionTearOff node) {
node.transformChildren(this);
return node.receiver;
}
@override
TreeNode visitAsExpression(AsExpression node) {
node.transformChildren(this);
return typeCastsOptimizer.transformAsExpression(node, typeContext);
}
}
class _AsyncStarFrame {
final VariableDeclaration controllerVar;
final VariableDeclaration pausedVar;
final DartType emittedValueType;
_AsyncStarFrame(this.controllerVar, this.pausedVar, this.emittedValueType);
}