8ef843ef4f
TEST=ci (co19/Language/Expressions/Function_Invocation/Function_Expression_Invocation/call_A04_t01) Change-Id: I49faf7c6f8d8f9353683dd94e9aa9a7ac7d30921 Cq-Include-Trybots: luci.dart.try:vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try,vm-dyn-linux-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443882 Reviewed-by: Tess Strickland <sstrickl@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
4557 lines
146 KiB
Dart
4557 lines
146 KiB
Dart
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:developer';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:kernel/ast.dart' hide Component, FunctionDeclaration;
|
|
import 'package:kernel/ast.dart' as ast show Component, FunctionDeclaration;
|
|
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
|
|
import 'package:kernel/core_types.dart' show CoreTypes;
|
|
import 'package:kernel/external_name.dart' show getExternalName;
|
|
import 'package:kernel/library_index.dart' show LibraryIndex;
|
|
import 'package:kernel/target/targets.dart' show Target;
|
|
import 'package:kernel/type_algebra.dart'
|
|
show Substitution, containsTypeParameter;
|
|
import 'package:kernel/type_environment.dart'
|
|
show StatefulStaticTypeContext, TypeEnvironment;
|
|
|
|
import 'package:vm/transformations/pragma.dart';
|
|
|
|
import 'assembler.dart';
|
|
import 'bytecode_serialization.dart'
|
|
show BufferedWriter, LinkWriter, StringTable;
|
|
import 'constant_pool.dart';
|
|
import 'dbc.dart';
|
|
import 'declarations.dart';
|
|
import 'exceptions.dart';
|
|
import 'generics.dart'
|
|
show
|
|
flattenInstantiatorTypeArguments,
|
|
getDefaultFunctionTypeArguments,
|
|
getInstantiatorTypeArguments,
|
|
getStaticType,
|
|
getTypeParameterTypes,
|
|
hasFreeTypeParameters,
|
|
hasInstantiatorTypeArguments,
|
|
isAllDynamic,
|
|
isInstantiatedInterfaceCall,
|
|
isUncheckedCall;
|
|
import 'local_variable_table.dart' show LocalVariableTable;
|
|
import 'local_vars.dart' show LocalVariables;
|
|
import 'object_table.dart'
|
|
show
|
|
ObjectHandle,
|
|
ObjectTable,
|
|
NameAndType,
|
|
ParameterFlags,
|
|
topLevelClassName;
|
|
import 'options.dart' show BytecodeOptions;
|
|
import 'recognized_methods.dart' show RecognizedMethods;
|
|
import 'source_positions.dart' show LineStarts, SourcePositions;
|
|
|
|
// This symbol is used as the name in assert assignable's to indicate it comes
|
|
// from an explicit 'as' check. This will cause the runtime to throw the right
|
|
// exception.
|
|
const String symbolForTypeCast = ' in type cast';
|
|
|
|
void generateBytecode(
|
|
ast.Component component,
|
|
Sink<List<int>> sink, {
|
|
required BytecodeOptions options,
|
|
required List<Library> libraries,
|
|
required CoreTypes coreTypes,
|
|
required ClassHierarchy hierarchy,
|
|
required Target target,
|
|
}) {
|
|
Timeline.timeSync("generateBytecode", () {
|
|
verifyBytecodeInstructionDeclarations();
|
|
final typeEnvironment = TypeEnvironment(coreTypes, hierarchy);
|
|
final pragmaParser = ConstantPragmaAnnotationParser(coreTypes, target);
|
|
|
|
final bytecodeGenerator = BytecodeGenerator(component, coreTypes, hierarchy,
|
|
typeEnvironment, options, pragmaParser);
|
|
for (Library library in libraries) {
|
|
bytecodeGenerator.visitLibrary(library);
|
|
}
|
|
|
|
final bytecodeComponent = bytecodeGenerator.bytecodeComponent;
|
|
|
|
final mainMethod = component.mainMethod;
|
|
if (mainMethod != null && bytecodeComponent.dynModuleEntryPoint == null) {
|
|
bytecodeComponent.dynModuleEntryPoint =
|
|
bytecodeComponent.objectTable.getHandle(mainMethod);
|
|
}
|
|
|
|
final linkWriter = new LinkWriter();
|
|
final writer = new BufferedWriter(bytecodeComponent.stringTable,
|
|
bytecodeComponent.objectTable, linkWriter);
|
|
bytecodeComponent.write(writer);
|
|
writer.writeContentsToSink(sink);
|
|
});
|
|
}
|
|
|
|
class BytecodeGenerator extends RecursiveVisitor {
|
|
static final Name callName = new Name('call');
|
|
static final Name noSuchMethodName = new Name('noSuchMethod');
|
|
|
|
final CoreTypes coreTypes;
|
|
final ClassHierarchy hierarchy;
|
|
final TypeEnvironment typeEnvironment;
|
|
final StatefulStaticTypeContext staticTypeContext;
|
|
final BytecodeOptions options;
|
|
final PragmaAnnotationParser pragmaParser;
|
|
final RecognizedMethods recognizedMethods;
|
|
final Map<Uri, Source> astUriToSource;
|
|
late StringTable stringTable;
|
|
late ObjectTable objectTable;
|
|
late Component bytecodeComponent;
|
|
|
|
List<ClassDeclaration> classDeclarations = const [];
|
|
List<FieldDeclaration> fieldDeclarations = const [];
|
|
List<FunctionDeclaration> functionDeclarations = const [];
|
|
Class? enclosingClass;
|
|
Member? enclosingMember;
|
|
FunctionNode? enclosingFunction;
|
|
FunctionNode? parentFunction;
|
|
bool isClosure = false;
|
|
Set<TypeParameter>? classTypeParameters;
|
|
List<TypeParameter>? functionTypeParameters;
|
|
Set<TypeParameter>? functionTypeParametersSet;
|
|
List<DartType>? instantiatorTypeArguments;
|
|
late LocalVariables locals;
|
|
Map<LabeledStatement, Label>? labeledStatements;
|
|
Map<SwitchCase, Label>? switchCases;
|
|
Map<TryCatch, TryBlock>? tryCatches;
|
|
Map<TryFinally, List<FinallyBlock>>? finallyBlocks;
|
|
TryBlock? asyncTryBlock;
|
|
Map<TreeNode, int>? contextLevels;
|
|
List<ClosureDeclaration>? closures;
|
|
Set<Field> initializedFields = const {};
|
|
List<ObjectHandle> nullableFields = const [];
|
|
late ConstantPool cp;
|
|
late BytecodeAssembler asm;
|
|
List<BytecodeAssembler>? savedAssemblers;
|
|
bool hasErrors = false;
|
|
int currentLoopDepth = 0;
|
|
List<int>? savedMaxSourcePositions;
|
|
int maxSourcePosition = 0;
|
|
Member? dynModuleEntryPoint;
|
|
|
|
BytecodeGenerator(
|
|
ast.Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
TypeEnvironment typeEnvironment,
|
|
BytecodeOptions options,
|
|
PragmaAnnotationParser pragmaParser)
|
|
: this._internal(
|
|
component,
|
|
coreTypes,
|
|
hierarchy,
|
|
typeEnvironment,
|
|
options,
|
|
pragmaParser,
|
|
StatefulStaticTypeContext.flat(typeEnvironment));
|
|
|
|
BytecodeGenerator._internal(
|
|
ast.Component component,
|
|
this.coreTypes,
|
|
this.hierarchy,
|
|
this.typeEnvironment,
|
|
this.options,
|
|
this.pragmaParser,
|
|
this.staticTypeContext)
|
|
: recognizedMethods = new RecognizedMethods(staticTypeContext),
|
|
astUriToSource = component.uriToSource {
|
|
bytecodeComponent = new Component(coreTypes);
|
|
stringTable = bytecodeComponent.stringTable;
|
|
objectTable = bytecodeComponent.objectTable;
|
|
}
|
|
|
|
@override
|
|
void visitLibrary(Library node) {
|
|
staticTypeContext.enterLibrary(node);
|
|
|
|
startMembers();
|
|
visitList(node.procedures, this);
|
|
visitList(node.fields, this);
|
|
final members = endMembers(node);
|
|
|
|
classDeclarations = <ClassDeclaration>[
|
|
getTopLevelClassDeclaration(node, members)
|
|
];
|
|
|
|
visitList(node.classes, this);
|
|
|
|
bytecodeComponent.libraries
|
|
.add(getLibraryDeclaration(node, classDeclarations));
|
|
classDeclarations = const [];
|
|
staticTypeContext.leaveLibrary(node);
|
|
}
|
|
|
|
@override
|
|
void visitClass(Class node) {
|
|
startMembers();
|
|
visitList(node.constructors, this);
|
|
visitList(node.procedures, this);
|
|
visitList(node.fields, this);
|
|
final members = endMembers(node);
|
|
|
|
classDeclarations.add(getClassDeclaration(node, members));
|
|
}
|
|
|
|
void startMembers() {
|
|
fieldDeclarations = <FieldDeclaration>[];
|
|
functionDeclarations = <FunctionDeclaration>[];
|
|
}
|
|
|
|
Members endMembers(TreeNode node) {
|
|
final members = new Members(fieldDeclarations, functionDeclarations);
|
|
bytecodeComponent.members.add(members);
|
|
fieldDeclarations = const [];
|
|
functionDeclarations = const [];
|
|
return members;
|
|
}
|
|
|
|
ObjectHandle getScript(Uri uri, bool includeSourceInfo) {
|
|
SourceFile? source;
|
|
if (options.emitSourcePositions) {
|
|
final astSource = astUriToSource[uri];
|
|
if (astSource != null) {
|
|
source = bytecodeComponent.uriToSource[uri];
|
|
if (source == null) {
|
|
final importUri =
|
|
objectTable.getConstStringHandle(astSource.importUri.toString());
|
|
source = new SourceFile(importUri);
|
|
bytecodeComponent.sourceFiles.add(source);
|
|
bytecodeComponent.uriToSource[uri] = source;
|
|
}
|
|
if (options.emitSourcePositions &&
|
|
includeSourceInfo &&
|
|
source.lineStarts == null) {
|
|
LineStarts lineStarts = new LineStarts(astSource.lineStarts!);
|
|
bytecodeComponent.lineStarts.add(lineStarts);
|
|
source.lineStarts = lineStarts;
|
|
}
|
|
}
|
|
}
|
|
return objectTable.getScriptHandle(uri, source);
|
|
}
|
|
|
|
LibraryDeclaration getLibraryDeclaration(
|
|
Library library, List<ClassDeclaration> classes) {
|
|
final importUri =
|
|
objectTable.getConstStringHandle(library.importUri.toString());
|
|
int flags = 0;
|
|
for (var dependency in library.dependencies) {
|
|
final targetLibrary = dependency.targetLibrary;
|
|
if (targetLibrary == coreTypes.mirrorsLibrary) {
|
|
flags |= LibraryDeclaration.usesDartMirrorsFlag;
|
|
} else if (targetLibrary == dartFfiLibrary) {
|
|
flags |= LibraryDeclaration.usesDartFfiFlag;
|
|
}
|
|
}
|
|
final name = objectTable.getPublicNameHandle(library.name ?? '');
|
|
final script = getScript(library.fileUri, true);
|
|
return new LibraryDeclaration(importUri, flags, name, script, classes);
|
|
}
|
|
|
|
ClassDeclaration getClassDeclaration(Class cls, Members members) {
|
|
int flags = 0;
|
|
if (cls.isAbstract) {
|
|
flags |= ClassDeclaration.isAbstractFlag;
|
|
}
|
|
if (cls.isEnum) {
|
|
flags |= ClassDeclaration.isEnumFlag;
|
|
}
|
|
if (cls.isSealed) {
|
|
flags |= ClassDeclaration.isSealedFlag;
|
|
}
|
|
if (cls.isMixinClass) {
|
|
flags |= ClassDeclaration.isMixinClassFlag;
|
|
}
|
|
if (cls.isBase) {
|
|
flags |= ClassDeclaration.isBaseClassFlag;
|
|
}
|
|
if (cls.isInterface) {
|
|
flags |= ClassDeclaration.isInterfaceFlag;
|
|
}
|
|
if (cls.isFinal) {
|
|
flags |= ClassDeclaration.isFinalFlag;
|
|
}
|
|
int numTypeArguments = 0;
|
|
TypeParametersDeclaration? typeParameters;
|
|
if (hasInstantiatorTypeArguments(cls)) {
|
|
flags |= ClassDeclaration.hasTypeArgumentsFlag;
|
|
numTypeArguments = flattenInstantiatorTypeArguments(
|
|
cls, getTypeParameterTypes(cls.typeParameters))
|
|
.length;
|
|
assert(numTypeArguments > 0);
|
|
if (cls.typeParameters.isNotEmpty) {
|
|
flags |= ClassDeclaration.hasTypeParamsFlag;
|
|
typeParameters = getTypeParametersDeclaration(cls.typeParameters);
|
|
}
|
|
}
|
|
if (cls.isEliminatedMixin) {
|
|
flags |= ClassDeclaration.isTransformedMixinApplicationFlag;
|
|
}
|
|
if (cls.hasConstConstructor) {
|
|
flags |= ClassDeclaration.hasConstConstructorFlag;
|
|
}
|
|
|
|
int position = TreeNode.noOffset;
|
|
int endPosition = TreeNode.noOffset;
|
|
if (options.emitSourcePositions && cls.fileOffset != TreeNode.noOffset) {
|
|
flags |= ClassDeclaration.hasSourcePositionsFlag;
|
|
position = cls.startFileOffset;
|
|
endPosition = cls.fileEndOffset;
|
|
}
|
|
Annotations annotations = getAnnotations(cls.annotations);
|
|
if (annotations.object != null) {
|
|
flags |= ClassDeclaration.hasAnnotationsFlag;
|
|
if (annotations.hasPragma) {
|
|
flags |= ClassDeclaration.hasPragmaFlag;
|
|
}
|
|
}
|
|
|
|
final nameHandle = objectTable.getNameHandle(
|
|
cls.name.startsWith('_') ? cls.enclosingLibrary : null, cls.name);
|
|
final script = getScript(cls.fileUri, !cls.isAnonymousMixin);
|
|
final superType = objectTable.getHandle(cls.supertype?.asInterfaceType);
|
|
final interfaces = objectTable.getNonNullHandles(
|
|
cls.implementedTypes.map((t) => t.asInterfaceType).toList());
|
|
|
|
final classDeclaration = new ClassDeclaration(
|
|
nameHandle,
|
|
flags,
|
|
script,
|
|
position,
|
|
endPosition,
|
|
typeParameters,
|
|
numTypeArguments,
|
|
superType,
|
|
interfaces,
|
|
members,
|
|
annotations.object);
|
|
bytecodeComponent.classes.add(classDeclaration);
|
|
return classDeclaration;
|
|
}
|
|
|
|
ClassDeclaration getTopLevelClassDeclaration(
|
|
Library library, Members members) {
|
|
int flags = 0;
|
|
int position = TreeNode.noOffset;
|
|
if (options.emitSourcePositions &&
|
|
library.fileOffset != TreeNode.noOffset) {
|
|
flags |= ClassDeclaration.hasSourcePositionsFlag;
|
|
position = library.fileOffset;
|
|
}
|
|
Annotations annotations = getLibraryAnnotations(library);
|
|
if (annotations.object != null) {
|
|
flags |= ClassDeclaration.hasAnnotationsFlag;
|
|
if (annotations.hasPragma) {
|
|
flags |= ClassDeclaration.hasPragmaFlag;
|
|
}
|
|
}
|
|
|
|
final nameHandle = objectTable.getPublicNameHandle(topLevelClassName);
|
|
final script = getScript(library.fileUri, true);
|
|
|
|
final classDeclaration = new ClassDeclaration(
|
|
nameHandle,
|
|
flags,
|
|
script,
|
|
position,
|
|
/* endPosition */ TreeNode.noOffset,
|
|
/* typeParameters */ null,
|
|
/* numTypeArguments */ 0,
|
|
/* superType */ null,
|
|
/* interfaces */ const <ObjectHandle>[],
|
|
members,
|
|
annotations.object);
|
|
bytecodeComponent.classes.add(classDeclaration);
|
|
return classDeclaration;
|
|
}
|
|
|
|
bool _isPragma(Constant annotation) =>
|
|
annotation is InstanceConstant &&
|
|
annotation.classNode == coreTypes.pragmaClass;
|
|
|
|
Annotations getAnnotations(List<Expression> nodes) {
|
|
if (nodes.isEmpty) {
|
|
return const Annotations(null, false);
|
|
}
|
|
List<Constant> constants = nodes.map(_getConstant).toList();
|
|
bool hasPragma = constants.any(_isPragma);
|
|
if (!options.emitAnnotations) {
|
|
if (hasPragma) {
|
|
constants = constants.where(_isPragma).toList();
|
|
} else {
|
|
return const Annotations(null, false);
|
|
}
|
|
}
|
|
final object = objectTable
|
|
.getHandle(new ListConstant(const DynamicType(), constants))!;
|
|
final decl = new AnnotationsDeclaration(object);
|
|
bytecodeComponent.annotations.add(decl);
|
|
return new Annotations(decl, hasPragma);
|
|
}
|
|
|
|
// Insert annotations for the function and its parameters into the annotations
|
|
// section. Return the annotations for the function only. The bytecode reader
|
|
// will implicitly find the parameter annotations by reading N packed objects
|
|
// after reading the function's annotations, one for each parameter.
|
|
Annotations getFunctionAnnotations(Member member) {
|
|
final functionNodes = member.annotations;
|
|
final parameterNodeLists = <List<Expression>>[];
|
|
for (VariableDeclaration variable
|
|
in member.function!.positionalParameters) {
|
|
parameterNodeLists.add(variable.annotations);
|
|
}
|
|
for (VariableDeclaration variable in member.function!.namedParameters) {
|
|
parameterNodeLists.add(variable.annotations);
|
|
}
|
|
|
|
if (functionNodes.isEmpty &&
|
|
parameterNodeLists.every((nodes) => nodes.isEmpty)) {
|
|
return const Annotations(null, false);
|
|
}
|
|
|
|
List<Constant> functionConstants = functionNodes.map(_getConstant).toList();
|
|
bool hasPragma = functionConstants.any(_isPragma);
|
|
if (!options.emitAnnotations && !hasPragma) {
|
|
return const Annotations(null, false);
|
|
}
|
|
|
|
final functionObject = objectTable
|
|
.getHandle(new ListConstant(const DynamicType(), functionConstants))!;
|
|
final functionDecl = new AnnotationsDeclaration(functionObject);
|
|
bytecodeComponent.annotations.add(functionDecl);
|
|
|
|
for (final parameterNodes in parameterNodeLists) {
|
|
List<Constant> parameterConstants =
|
|
parameterNodes.map(_getConstant).toList();
|
|
final parameterObject = objectTable.getHandle(
|
|
new ListConstant(const DynamicType(), parameterConstants))!;
|
|
final parameterDecl = new AnnotationsDeclaration(parameterObject);
|
|
bytecodeComponent.annotations.add(parameterDecl);
|
|
}
|
|
|
|
return new Annotations(functionDecl, hasPragma);
|
|
}
|
|
|
|
// Insert annotations for library and its dependencies into the
|
|
// annotations section. Returns annotations for the library only.
|
|
// Bytecode reader will implicitly find library dependencies by reading
|
|
// an extra object after reading library annotations.
|
|
Annotations getLibraryAnnotations(Library library) {
|
|
Annotations annotations = getAnnotations(library.annotations);
|
|
final bool emitDependencies =
|
|
options.emitAnnotations && library.dependencies.isNotEmpty;
|
|
if (annotations.object == null && !emitDependencies) {
|
|
return annotations;
|
|
}
|
|
|
|
// We need to emit both annotations and dependencies objects, appending
|
|
// null if an object is missing.
|
|
if (annotations.object == null) {
|
|
final annotationsDecl = new AnnotationsDeclaration(null);
|
|
bytecodeComponent.annotations.add(annotationsDecl);
|
|
annotations = new Annotations(annotationsDecl, false);
|
|
}
|
|
if (!emitDependencies) {
|
|
bytecodeComponent.annotations.add(new AnnotationsDeclaration(null));
|
|
return annotations;
|
|
}
|
|
|
|
// Create a constant object representing library dependencies.
|
|
// These objects are used by dart:mirrors and vm-service implementation.
|
|
final deps = <Constant>[];
|
|
for (var dependency in library.dependencies) {
|
|
final dependencyName = dependency.name;
|
|
final prefix = dependencyName != null
|
|
? StringConstant(dependencyName)
|
|
: NullConstant();
|
|
final showNames = dependency.combinators
|
|
.where((c) => c.isShow)
|
|
.expand((c) => c.names)
|
|
.map((name) => StringConstant(name))
|
|
.toList();
|
|
final hideNames = dependency.combinators
|
|
.where((c) => c.isHide)
|
|
.expand((c) => c.names)
|
|
.map((name) => StringConstant(name))
|
|
.toList();
|
|
final depAnnots = dependency.annotations.map(_getConstant).toList();
|
|
deps.add(ListConstant(const DynamicType(), <Constant>[
|
|
StringConstant(dependency.targetLibrary.importUri.toString()),
|
|
BoolConstant(dependency.isExport),
|
|
BoolConstant(dependency.isDeferred),
|
|
prefix,
|
|
ListConstant(const DynamicType(), showNames),
|
|
ListConstant(const DynamicType(), hideNames),
|
|
ListConstant(const DynamicType(), depAnnots),
|
|
]));
|
|
}
|
|
final ObjectHandle dependenciesObject =
|
|
objectTable.getHandle(ListConstant(const DynamicType(), deps))!;
|
|
final dependenciesDecl = new AnnotationsDeclaration(dependenciesObject);
|
|
bytecodeComponent.annotations.add(dependenciesDecl);
|
|
|
|
return annotations;
|
|
}
|
|
|
|
FieldDeclaration getFieldDeclaration(Field field, Code? initializer) {
|
|
int flags = 0;
|
|
Constant? value;
|
|
final astInitializer = field.initializer;
|
|
if (_hasNonTrivialInitializer(field)) {
|
|
flags |= FieldDeclaration.hasNontrivialInitializerFlag;
|
|
} else if (astInitializer != null) {
|
|
value = _getConstant(astInitializer);
|
|
}
|
|
if (initializer != null) {
|
|
flags |= FieldDeclaration.hasInitializerCodeFlag;
|
|
}
|
|
if (astInitializer != null) {
|
|
flags |= FieldDeclaration.hasInitializerFlag;
|
|
}
|
|
final name = objectTable.getNameHandle(
|
|
field.name.library, objectTable.mangleMemberName(field, false, false));
|
|
ObjectHandle? getterName;
|
|
ObjectHandle? setterName;
|
|
if (_needsGetter(field)) {
|
|
flags |= FieldDeclaration.hasGetterFlag;
|
|
getterName = objectTable.getNameHandle(
|
|
field.name.library, objectTable.mangleMemberName(field, true, false));
|
|
}
|
|
if (_needsSetter(field)) {
|
|
flags |= FieldDeclaration.hasSetterFlag;
|
|
setterName = objectTable.getNameHandle(
|
|
field.name.library, objectTable.mangleMemberName(field, false, true));
|
|
}
|
|
if (isReflectable(field)) {
|
|
flags |= FieldDeclaration.isReflectableFlag;
|
|
}
|
|
if (field.isStatic) {
|
|
flags |= FieldDeclaration.isStaticFlag;
|
|
}
|
|
if (field.isConst) {
|
|
flags |= FieldDeclaration.isConstFlag;
|
|
}
|
|
// Const fields are implicitly final.
|
|
if (field.isConst || field.isFinal) {
|
|
flags |= FieldDeclaration.isFinalFlag;
|
|
}
|
|
if (field.isCovariantByDeclaration) {
|
|
flags |= FieldDeclaration.isCovariantFlag;
|
|
}
|
|
if (field.isCovariantByClass) {
|
|
flags |= FieldDeclaration.isCovariantByClassFlag;
|
|
}
|
|
if (field.isExtensionMember) {
|
|
flags |= FieldDeclaration.isExtensionMemberFlag;
|
|
}
|
|
// In NNBD libraries, static fields with initializers are implicitly late.
|
|
if (field.isLate || (field.isStatic && field.initializer != null)) {
|
|
flags |= FieldDeclaration.isLateFlag;
|
|
}
|
|
int position = TreeNode.noOffset;
|
|
int endPosition = TreeNode.noOffset;
|
|
if (options.emitSourcePositions && field.fileOffset != TreeNode.noOffset) {
|
|
flags |= FieldDeclaration.hasSourcePositionsFlag;
|
|
position = field.fileOffset;
|
|
endPosition = field.fileEndOffset;
|
|
}
|
|
Annotations annotations = getAnnotations(field.annotations);
|
|
if (annotations.object != null) {
|
|
flags |= FieldDeclaration.hasAnnotationsFlag;
|
|
if (annotations.hasPragma) {
|
|
flags |= FieldDeclaration.hasPragmaFlag;
|
|
}
|
|
}
|
|
ObjectHandle? script;
|
|
if (field.fileUri != (field.parent as FileUriNode).fileUri) {
|
|
final isInAnonymousMixin = enclosingClass?.isAnonymousMixin ?? false;
|
|
script = getScript(field.fileUri, !isInAnonymousMixin);
|
|
flags |= FieldDeclaration.hasCustomScriptFlag;
|
|
}
|
|
return new FieldDeclaration(
|
|
flags,
|
|
name,
|
|
objectTable.getHandle(field.type)!,
|
|
objectTable.getHandle(value),
|
|
script,
|
|
position,
|
|
endPosition,
|
|
getterName,
|
|
setterName,
|
|
initializer,
|
|
annotations.object);
|
|
}
|
|
|
|
FunctionDeclaration getFunctionDeclaration(Member member, Code? code) {
|
|
int flags = 0;
|
|
if (member is Constructor) {
|
|
flags |= FunctionDeclaration.isConstructorFlag;
|
|
}
|
|
if (member is Procedure) {
|
|
if (member.isGetter) {
|
|
flags |= FunctionDeclaration.isGetterFlag;
|
|
} else if (member.isSetter) {
|
|
flags |= FunctionDeclaration.isSetterFlag;
|
|
} else if (member.isFactory) {
|
|
flags |= FunctionDeclaration.isFactoryFlag;
|
|
}
|
|
if (member.isStatic) {
|
|
flags |= FunctionDeclaration.isStaticFlag;
|
|
}
|
|
if (member.isNoSuchMethodForwarder) {
|
|
flags |= FunctionDeclaration.isNoSuchMethodForwarderFlag;
|
|
}
|
|
}
|
|
if (member.isAbstract && !_hasCode(member)) {
|
|
flags |= FunctionDeclaration.isAbstractFlag;
|
|
}
|
|
if (member.isConst) {
|
|
flags |= FunctionDeclaration.isConstFlag;
|
|
}
|
|
if (member.isExtensionMember) {
|
|
flags |= FunctionDeclaration.isExtensionMemberFlag;
|
|
}
|
|
|
|
FunctionNode function = member.function!;
|
|
if (function.requiredParameterCount !=
|
|
function.positionalParameters.length) {
|
|
flags |= FunctionDeclaration.hasOptionalPositionalParamsFlag;
|
|
}
|
|
if (function.namedParameters.isNotEmpty) {
|
|
flags |= FunctionDeclaration.hasOptionalNamedParamsFlag;
|
|
}
|
|
TypeParametersDeclaration? typeParameters;
|
|
if (function.typeParameters.isNotEmpty) {
|
|
flags |= FunctionDeclaration.hasTypeParamsFlag;
|
|
typeParameters = getTypeParametersDeclaration(function.typeParameters);
|
|
}
|
|
if (isReflectable(member)) {
|
|
flags |= FunctionDeclaration.isReflectableFlag;
|
|
}
|
|
if (isDebuggable(member)) {
|
|
flags |= FunctionDeclaration.isDebuggableFlag;
|
|
}
|
|
switch (function.dartAsyncMarker) {
|
|
case AsyncMarker.Async:
|
|
flags |= FunctionDeclaration.isAsyncFlag;
|
|
break;
|
|
case AsyncMarker.AsyncStar:
|
|
flags |= FunctionDeclaration.isAsyncStarFlag;
|
|
break;
|
|
case AsyncMarker.SyncStar:
|
|
flags |= FunctionDeclaration.isSyncStarFlag;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ObjectHandle? nativeName;
|
|
if (member.isExternal) {
|
|
final String? externalName = getExternalName(coreTypes, member);
|
|
if (externalName == null) {
|
|
flags |= FunctionDeclaration.isExternalFlag;
|
|
} else {
|
|
flags |= FunctionDeclaration.isNativeFlag;
|
|
nativeName = objectTable.getConstStringHandle(externalName);
|
|
}
|
|
}
|
|
int position = TreeNode.noOffset;
|
|
int endPosition = TreeNode.noOffset;
|
|
if (options.emitSourcePositions && member.fileOffset != TreeNode.noOffset) {
|
|
flags |= FunctionDeclaration.hasSourcePositionsFlag;
|
|
if (member is Constructor) {
|
|
position = member.startFileOffset;
|
|
} else if (member is Procedure) {
|
|
position = member.fileStartOffset;
|
|
} else {
|
|
throw 'Unexpected ${member.runtimeType} $member';
|
|
}
|
|
endPosition = member.fileEndOffset;
|
|
}
|
|
final Annotations annotations = getFunctionAnnotations(member);
|
|
if (annotations.object != null) {
|
|
flags |= FunctionDeclaration.hasAnnotationsFlag;
|
|
if (annotations.hasPragma) {
|
|
flags |= FunctionDeclaration.hasPragmaFlag;
|
|
if (pragmaParser
|
|
.parsedPragmas<ParsedDynModuleEntryPointPragma>(member.annotations)
|
|
.isNotEmpty) {
|
|
if (dynModuleEntryPoint != null) {
|
|
throw 'Duplicate Dynamic Module Entry Points: $dynModuleEntryPoint and $member';
|
|
}
|
|
if (!(member is Procedure &&
|
|
member.isStatic &&
|
|
function.typeParameters.isEmpty &&
|
|
function.positionalParameters.isEmpty &&
|
|
function.namedParameters.isEmpty)) {
|
|
throw 'Dynamic Module Entry Point should be a static no-argument method: $member';
|
|
}
|
|
dynModuleEntryPoint = member;
|
|
bytecodeComponent.dynModuleEntryPoint = objectTable.getHandle(member);
|
|
}
|
|
}
|
|
}
|
|
ObjectHandle? script;
|
|
if (member.fileUri != (member.parent as FileUriNode).fileUri) {
|
|
final isInAnonymousMixin = enclosingClass?.isAnonymousMixin ?? false;
|
|
final isSynthetic = member is Procedure &&
|
|
(member.isNoSuchMethodForwarder || member.isSyntheticForwarder);
|
|
script = getScript(member.fileUri, !isInAnonymousMixin && !isSynthetic);
|
|
flags |= FunctionDeclaration.hasCustomScriptFlag;
|
|
}
|
|
|
|
final name = objectTable.getNameHandle(member.name.library,
|
|
objectTable.mangleMemberName(member, false, false));
|
|
|
|
final parameters = <ParameterDeclaration>[];
|
|
for (var param in function.positionalParameters) {
|
|
parameters.add(getParameterDeclaration(param));
|
|
}
|
|
for (var param in function.namedParameters) {
|
|
parameters.add(getParameterDeclaration(param));
|
|
}
|
|
final parameterFlags =
|
|
ParameterFlags.getFunctionFlags(function, isCode: false);
|
|
if (parameterFlags != null) {
|
|
flags |= FunctionDeclaration.hasParameterFlagsFlag;
|
|
}
|
|
|
|
return new FunctionDeclaration(
|
|
flags,
|
|
name,
|
|
script,
|
|
position,
|
|
endPosition,
|
|
typeParameters,
|
|
function.requiredParameterCount,
|
|
parameters,
|
|
parameterFlags,
|
|
objectTable.getHandle(function.returnType)!,
|
|
nativeName,
|
|
code,
|
|
annotations.object);
|
|
}
|
|
|
|
bool isReflectable(Member member) {
|
|
if (member is Field && member.fileOffset == TreeNode.noOffset) {
|
|
return false;
|
|
}
|
|
final library = member.enclosingLibrary;
|
|
if (library.importUri.scheme == 'dart' && member.name.isPrivate) {
|
|
return false;
|
|
}
|
|
if (member is Procedure &&
|
|
member.isStatic &&
|
|
library.importUri.toString() == 'dart:_internal') {
|
|
return false;
|
|
}
|
|
if (member is Procedure && member.isMemberSignature) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isDebuggable(Member member) {
|
|
if (member is Constructor && member.isSynthetic) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TypeParametersDeclaration getTypeParametersDeclaration(
|
|
List<TypeParameter> typeParams) {
|
|
return new TypeParametersDeclaration(
|
|
objectTable.getTypeParameterHandles(typeParams));
|
|
}
|
|
|
|
ParameterDeclaration getParameterDeclaration(VariableDeclaration variable) {
|
|
final name = variable.name!;
|
|
final lib = name.startsWith('_') ? enclosingMember!.enclosingLibrary : null;
|
|
final nameHandle = objectTable.getNameHandle(lib, name);
|
|
final typeHandle = objectTable.getHandle(variable.type)!;
|
|
return new ParameterDeclaration(nameHandle, typeHandle);
|
|
}
|
|
|
|
@override
|
|
void defaultMember(Member node) {
|
|
final bool hasCode = _hasCode(node);
|
|
start(node, hasCode);
|
|
if (node is Field) {
|
|
if (hasCode) {
|
|
if (node.isConst) {
|
|
_genPushConstExpr(node.initializer!);
|
|
} else {
|
|
_generateNode(node.initializer!);
|
|
}
|
|
_genReturnTOS();
|
|
}
|
|
} else if (node is Procedure || node is Constructor) {
|
|
if (hasCode) {
|
|
if (node is Constructor) {
|
|
_genConstructorInitializers(node);
|
|
}
|
|
if (node.isExternal) {
|
|
if (getExternalName(coreTypes, node) != null) {
|
|
_genExternalCall(node);
|
|
} else {
|
|
_genNoSuchMethodForExternal(node);
|
|
}
|
|
} else {
|
|
_generateNode(node.function?.body);
|
|
// BytecodeAssembler eliminates this bytecode if it is unreachable.
|
|
asm.emitPushNull();
|
|
}
|
|
if (node.function != null) {
|
|
_recordSourcePosition(node.function!.fileEndOffset);
|
|
}
|
|
_genReturnTOS();
|
|
}
|
|
} else {
|
|
throw 'Unexpected member ${node.runtimeType} $node';
|
|
}
|
|
end(node, hasCode);
|
|
}
|
|
|
|
bool _hasCode(Member member) {
|
|
// Front-end might set abstract flag on static external procedures,
|
|
// but they can be called and should have a body.
|
|
if (member is Procedure && member.isStatic && member.isExternal) {
|
|
return true;
|
|
}
|
|
if (member.isAbstract) {
|
|
return false;
|
|
}
|
|
if (member is Field) {
|
|
return hasInitializerCode(member);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool hasInitializerCode(Field field) =>
|
|
(field.isStatic ||
|
|
field.isLate ||
|
|
options.emitInstanceFieldInitializers) &&
|
|
_hasNonTrivialInitializer(field);
|
|
|
|
bool _needsGetter(Field field) {
|
|
// All instance fields need a getter.
|
|
if (!field.isStatic) return true;
|
|
|
|
// Static fields also need a getter if they have a non-trivial initializer,
|
|
// because it needs to be initialized lazily.
|
|
if (_hasNonTrivialInitializer(field)) return true;
|
|
|
|
// Static late fields with no initializer also need a getter, to check if
|
|
// it's been initialized.
|
|
return field.isLate && field.initializer == null;
|
|
}
|
|
|
|
bool _needsSetter(Field field) {
|
|
// Final fields don't have a setter, except late final fields
|
|
// without initializer.
|
|
if (field.isFinal) {
|
|
// Late final fields without initializer always need a setter to check
|
|
// if they are already initialized.
|
|
if (field.isLate && (field.initializer == null)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Instance non-final fields always need a setter.
|
|
if (!field.isStatic) return true;
|
|
|
|
// Otherwise, setters for static fields can be omitted
|
|
// and fields can be accessed directly.
|
|
return false;
|
|
}
|
|
|
|
void _genExternalCall(Member node) {
|
|
final function = node.function!;
|
|
|
|
if (locals.hasFactoryTypeArgsVar) {
|
|
asm.emitPush(locals.getVarIndexInFrame(locals.factoryTypeArgsVar));
|
|
} else if (locals.hasFunctionTypeArgsVar) {
|
|
asm.emitPush(locals.functionTypeArgsVarIndexInFrame);
|
|
}
|
|
if (locals.hasReceiver) {
|
|
asm.emitPush(locals.getVarIndexInFrame(locals.receiverVar));
|
|
}
|
|
for (var param in function.positionalParameters) {
|
|
asm.emitPush(locals.getVarIndexInFrame(param));
|
|
}
|
|
// Native methods access their parameters by indices, so
|
|
// native wrappers should pass arguments in the original declaration
|
|
// order instead of sorted order.
|
|
for (var param in locals.originalNamedParameters) {
|
|
asm.emitPush(locals.getVarIndexInFrame(param));
|
|
}
|
|
|
|
final externalCallCpIndex = cp.addExternalCall();
|
|
asm.emitExternalCall(externalCallCpIndex);
|
|
}
|
|
|
|
LibraryIndex get libraryIndex => coreTypes.index;
|
|
|
|
late Procedure growableListLiteral =
|
|
libraryIndex.getProcedure('dart:core', '_GrowableList', '_literal');
|
|
|
|
late Procedure mapFromLiteral =
|
|
libraryIndex.getProcedure('dart:core', 'Map', '_fromLiteral');
|
|
|
|
late Procedure interpolateSingle = libraryIndex.getProcedure(
|
|
'dart:core', '_StringBase', '_interpolateSingle');
|
|
|
|
late Procedure interpolate =
|
|
libraryIndex.getProcedure('dart:core', '_StringBase', '_interpolate');
|
|
|
|
late Class closureClass = libraryIndex.getClass('dart:core', '_Closure');
|
|
|
|
late Procedure objectInstanceOf =
|
|
libraryIndex.getProcedure('dart:core', 'Object', '_instanceOf');
|
|
|
|
late Procedure objectSimpleInstanceOf =
|
|
libraryIndex.getProcedure('dart:core', 'Object', '_simpleInstanceOf');
|
|
|
|
late Field closureInstantiatorTypeArguments = libraryIndex.getField(
|
|
'dart:core', '_Closure', '_instantiator_type_arguments');
|
|
|
|
late Field closureFunctionTypeArguments = libraryIndex.getField(
|
|
'dart:core', '_Closure', '_function_type_arguments');
|
|
|
|
late Field closureDelayedTypeArguments =
|
|
libraryIndex.getField('dart:core', '_Closure', '_delayed_type_arguments');
|
|
|
|
late Field closureFunction =
|
|
libraryIndex.getField('dart:core', '_Closure', '_function');
|
|
|
|
late Field closureContext =
|
|
libraryIndex.getField('dart:core', '_Closure', '_context');
|
|
|
|
late Procedure prependTypeArguments = libraryIndex.getTopLevelProcedure(
|
|
'dart:_internal', '_prependTypeArguments');
|
|
|
|
late Procedure boundsCheckForPartialInstantiation =
|
|
libraryIndex.getTopLevelProcedure(
|
|
'dart:_internal', '_boundsCheckForPartialInstantiation');
|
|
|
|
late Procedure futureValue =
|
|
libraryIndex.getProcedure('dart:async', 'Future', 'value');
|
|
|
|
late Procedure throwLocalNotInitialized = libraryIndex.getProcedure(
|
|
'dart:_internal', 'LateError', '_throwLocalNotInitialized');
|
|
|
|
late Procedure throwLocalAlreadyInitialized = libraryIndex.getProcedure(
|
|
'dart:_internal', 'LateError', '_throwLocalAlreadyInitialized');
|
|
|
|
late Procedure throwLocalAssignedDuringInitialization =
|
|
libraryIndex.getProcedure('dart:_internal', 'LateError',
|
|
'_throwLocalAssignedDuringInitialization');
|
|
|
|
late Procedure throwNewSourceAssertionError = libraryIndex.getProcedure(
|
|
'dart:core', '_AssertionError', '_throwNewSource');
|
|
|
|
late Procedure throwNewNoSuchMethodError =
|
|
libraryIndex.getProcedure('dart:core', 'NoSuchMethodError', '_throwNew');
|
|
|
|
late Procedure allocateInvocationMirror = libraryIndex.getProcedure(
|
|
'dart:core', '_InvocationMirror', '_allocateInvocationMirror');
|
|
|
|
late Procedure unsafeCast =
|
|
libraryIndex.getTopLevelProcedure('dart:_internal', 'unsafeCast');
|
|
|
|
late Procedure reachabilityFence =
|
|
libraryIndex.getTopLevelProcedure('dart:_internal', 'reachabilityFence');
|
|
|
|
late Procedure nativeEffect =
|
|
libraryIndex.getTopLevelProcedure('dart:_internal', '_nativeEffect');
|
|
|
|
late Procedure iterableIterator =
|
|
libraryIndex.getProcedure('dart:core', 'Iterable', 'get:iterator');
|
|
|
|
late Procedure iteratorMoveNext =
|
|
libraryIndex.getProcedure('dart:core', 'Iterator', 'moveNext');
|
|
|
|
late Procedure iteratorCurrent =
|
|
libraryIndex.getProcedure('dart:core', 'Iterator', 'get:current');
|
|
|
|
late Procedure setAsyncThreadStackTrace = libraryIndex.getTopLevelProcedure(
|
|
'dart:async', '_setAsyncThreadStackTrace');
|
|
|
|
late Procedure clearAsyncThreadStackTrace = libraryIndex.getTopLevelProcedure(
|
|
'dart:async', '_clearAsyncThreadStackTrace');
|
|
|
|
late Procedure initAsync =
|
|
libraryIndex.getProcedure('dart:async', '_SuspendState', '_initAsync');
|
|
|
|
late Procedure suspendStateFunctionData = libraryIndex.getProcedure(
|
|
'dart:async',
|
|
'_SuspendState',
|
|
LibraryIndex.getterPrefix + '_functionData');
|
|
|
|
late Procedure initAsyncStar = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_initAsyncStar');
|
|
|
|
late Procedure initSyncStar =
|
|
libraryIndex.getProcedure('dart:async', '_SuspendState', '_initSyncStar');
|
|
|
|
late Procedure _await =
|
|
libraryIndex.getProcedure('dart:async', '_SuspendState', '_await');
|
|
|
|
late Procedure _awaitWithTypeCheck = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_awaitWithTypeCheck');
|
|
|
|
late Procedure yieldAsyncStar = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_yieldAsyncStar');
|
|
|
|
late Procedure suspendSyncStarAtStart = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_suspendSyncStarAtStart');
|
|
|
|
late Procedure returnAsync =
|
|
libraryIndex.getProcedure('dart:async', '_SuspendState', '_returnAsync');
|
|
|
|
late Procedure returnAsyncStar = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_returnAsyncStar');
|
|
|
|
late Procedure handleException = libraryIndex.getProcedure(
|
|
'dart:async', '_SuspendState', '_handleException');
|
|
|
|
late Procedure asyncStarStreamControllerAdd = libraryIndex.getProcedure(
|
|
'dart:async', '_AsyncStarStreamController', 'add');
|
|
|
|
late Procedure asyncStarStreamControllerAddStream = libraryIndex.getProcedure(
|
|
'dart:async', '_AsyncStarStreamController', 'addStream');
|
|
|
|
late Field syncStarIteratorCurrent =
|
|
libraryIndex.getField('dart:async', '_SyncStarIterator', '_current');
|
|
|
|
late Field syncStarIteratorYieldStarIterable = libraryIndex.getField(
|
|
'dart:async', '_SyncStarIterator', '_yieldStarIterable');
|
|
|
|
late Library? dartFfiLibrary = libraryIndex.tryGetLibrary('dart:ffi');
|
|
|
|
// Selector for implicit dynamic calls 'foo(...)' where
|
|
// variable 'foo' has type 'dynamic'.
|
|
late final implicitCallName = Name('implicit:call');
|
|
|
|
void _recordSourcePosition(int fileOffset) {
|
|
asm.currentSourcePosition = fileOffset;
|
|
maxSourcePosition = math.max(maxSourcePosition, fileOffset);
|
|
}
|
|
|
|
void _generateNode(TreeNode? node) {
|
|
if (node == null) {
|
|
return;
|
|
}
|
|
final savedSourcePosition = asm.currentSourcePosition;
|
|
_recordSourcePosition(node.fileOffset);
|
|
node.accept(this);
|
|
asm.currentSourcePosition = savedSourcePosition;
|
|
}
|
|
|
|
void _generateNodeList(List<TreeNode> nodes) {
|
|
nodes.forEach(_generateNode);
|
|
}
|
|
|
|
void _genConstructorInitializers(Constructor node) {
|
|
bool isRedirecting = false;
|
|
Set<Field> initializedInInitializersList = new Set<Field>();
|
|
for (var initializer in node.initializers) {
|
|
if (initializer is RedirectingInitializer) {
|
|
isRedirecting = true;
|
|
} else if (initializer is FieldInitializer) {
|
|
initializedInInitializersList.add(initializer.field);
|
|
}
|
|
}
|
|
|
|
if (!isRedirecting) {
|
|
initializedFields = Set<Field>();
|
|
for (var field in node.enclosingClass.fields) {
|
|
if (!field.isStatic) {
|
|
if (field.isLate) {
|
|
if (!initializedInInitializersList.contains(field)) {
|
|
_genLateFieldInitializer(field);
|
|
}
|
|
} else {
|
|
final fieldInitializer = field.initializer;
|
|
if (fieldInitializer != null) {
|
|
if (initializedInInitializersList.contains(field)) {
|
|
// Do not store a value into the field as it is going to be
|
|
// overwritten by initializers list.
|
|
_generateNode(fieldInitializer);
|
|
asm.emitDrop1();
|
|
} else {
|
|
_genFieldInitializer(field, fieldInitializer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_generateNodeList(node.initializers);
|
|
|
|
if (!isRedirecting) {
|
|
nullableFields = <ObjectHandle>[];
|
|
for (var field in node.enclosingClass.fields) {
|
|
if (!field.isStatic &&
|
|
!field.isLate &&
|
|
!initializedFields.contains(field)) {
|
|
nullableFields.add(objectTable.getHandle(field)!);
|
|
}
|
|
}
|
|
initializedFields = const {}; // No more initialized fields, please.
|
|
}
|
|
}
|
|
|
|
void _genFieldInitializer(Field field, Expression initializer) {
|
|
assert(!field.isStatic);
|
|
|
|
if (initializer is NullLiteral && !initializedFields.contains(field)) {
|
|
return;
|
|
}
|
|
|
|
_genPushReceiver();
|
|
_generateNode(initializer);
|
|
|
|
final int cpIndex = cp.addInstanceField(field);
|
|
asm.emitStoreFieldTOS(cpIndex);
|
|
|
|
initializedFields.add(field);
|
|
}
|
|
|
|
void _genLateFieldInitializer(Field field) {
|
|
assert(!field.isStatic);
|
|
|
|
if (_isTrivialInitializer(field.initializer)) {
|
|
_genFieldInitializer(field, field.initializer!);
|
|
return;
|
|
}
|
|
|
|
_genPushReceiver();
|
|
|
|
final int cpIndex = cp.addInstanceField(field);
|
|
asm.emitInitLateField(cpIndex);
|
|
|
|
initializedFields.add(field);
|
|
}
|
|
|
|
void _genArguments(Expression? receiver, Arguments arguments,
|
|
{int? storeReceiverToLocal}) {
|
|
if (arguments.types.isNotEmpty) {
|
|
_genTypeArguments(arguments.types);
|
|
}
|
|
_generateNode(receiver);
|
|
if (storeReceiverToLocal != null) {
|
|
asm.emitStoreLocal(storeReceiverToLocal);
|
|
}
|
|
_generateNodeList(arguments.positional);
|
|
arguments.named.forEach((NamedExpression ne) => _generateNode(ne.value));
|
|
}
|
|
|
|
void _genPushBool(bool value) {
|
|
if (value) {
|
|
asm.emitPushTrue();
|
|
} else {
|
|
asm.emitPushFalse();
|
|
}
|
|
}
|
|
|
|
void _genPushInt(int value) {
|
|
// TODO(alexmarkov): relax this constraint as PushInt instruction can
|
|
// hold up to 32-bit signed operand (note that interpreter assumes
|
|
// it is Smi).
|
|
if (value.bitLength + 1 <= 16) {
|
|
asm.emitPushInt(value);
|
|
} else {
|
|
asm.emitPushConstant(cp.addObjectRef(new IntConstant(value)));
|
|
}
|
|
}
|
|
|
|
Constant _getConstant(Expression expr) {
|
|
if (expr is ConstantExpression) {
|
|
return expr.constant;
|
|
}
|
|
|
|
// Literals outside of const expressions are not transformed by the
|
|
// constant transformer, but they need to be treated as constants here.
|
|
if (expr is BoolLiteral) return new BoolConstant(expr.value);
|
|
if (expr is DoubleLiteral) return new DoubleConstant(expr.value);
|
|
if (expr is IntLiteral) return new IntConstant(expr.value);
|
|
if (expr is NullLiteral) return new NullConstant();
|
|
if (expr is StringLiteral) return new StringConstant(expr.value);
|
|
|
|
throw 'Expected constant, got ${expr.runtimeType}';
|
|
}
|
|
|
|
void _genPushConstant(Constant constant) {
|
|
if (constant is NullConstant) {
|
|
asm.emitPushNull();
|
|
} else if (constant is BoolConstant) {
|
|
_genPushBool(constant.value);
|
|
} else if (constant is IntConstant) {
|
|
_genPushInt(constant.value);
|
|
} else {
|
|
asm.emitPushConstant(cp.addObjectRef(constant));
|
|
}
|
|
}
|
|
|
|
void _genPushConstExpr(Expression expr) {
|
|
if (expr is ConstantExpression) {
|
|
_genPushConstant(expr.constant);
|
|
} else if (expr is NullLiteral) {
|
|
asm.emitPushNull();
|
|
} else if (expr is BoolLiteral) {
|
|
_genPushBool(expr.value);
|
|
} else if (expr is IntLiteral) {
|
|
_genPushInt(expr.value);
|
|
} else {
|
|
_genPushConstant(_getConstant(expr));
|
|
}
|
|
}
|
|
|
|
void _genReturnTOS() {
|
|
final enclosingFunction = this.enclosingFunction;
|
|
if (enclosingFunction != null) {
|
|
Procedure? returnMethod;
|
|
switch (enclosingFunction.dartAsyncMarker) {
|
|
case AsyncMarker.Async:
|
|
returnMethod = returnAsync;
|
|
break;
|
|
case AsyncMarker.AsyncStar:
|
|
returnMethod = returnAsyncStar;
|
|
break;
|
|
case AsyncMarker.SyncStar:
|
|
asm.emitDrop1();
|
|
asm.emitPushFalse();
|
|
break;
|
|
case AsyncMarker.Sync:
|
|
break;
|
|
}
|
|
if (returnMethod != null) {
|
|
asm.emitPopLocal(locals.returnVarIndexInFrame);
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPush(locals.returnVarIndexInFrame);
|
|
asm.emitPushNull();
|
|
asm.emitPopLocal(locals.suspendStateVarIndexInFrame);
|
|
_genDirectCall(returnMethod, objectTable.getArgDescHandle(2), 2);
|
|
}
|
|
}
|
|
asm.emitReturnTOS();
|
|
}
|
|
|
|
void _genDirectCall(Member target, ObjectHandle argDesc, int totalArgCount,
|
|
{bool isGet = false,
|
|
bool isSet = false,
|
|
bool isUnchecked = false,
|
|
TreeNode? node}) {
|
|
assert(!isGet || !isSet);
|
|
final kind = isGet
|
|
? InvocationKind.getter
|
|
: (isSet ? InvocationKind.setter : InvocationKind.method);
|
|
final cpIndex = cp.addDirectCall(kind, target, argDesc);
|
|
|
|
if (totalArgCount >= argumentsLimit) {
|
|
throw 'Too many arguments';
|
|
}
|
|
if (isUnchecked) {
|
|
asm.emitUncheckedDirectCall(cpIndex, totalArgCount);
|
|
} else {
|
|
asm.emitDirectCall(cpIndex, totalArgCount);
|
|
}
|
|
}
|
|
|
|
void _genDirectCallWithArgs(Member target, Arguments args,
|
|
{bool hasReceiver = false,
|
|
bool isFactory = false,
|
|
bool isUnchecked = false,
|
|
TreeNode? node}) {
|
|
final argDesc = objectTable.getArgDescHandleByArguments(args,
|
|
hasReceiver: hasReceiver, isFactory: isFactory);
|
|
|
|
int totalArgCount = args.positional.length + args.named.length;
|
|
if (hasReceiver) {
|
|
totalArgCount++;
|
|
}
|
|
if (args.types.isNotEmpty || isFactory) {
|
|
// VM needs type arguments for every invocation of a factory constructor.
|
|
// TODO(alexmarkov): Clean this up.
|
|
totalArgCount++;
|
|
}
|
|
|
|
_genDirectCall(target, argDesc, totalArgCount,
|
|
isUnchecked: isUnchecked, node: node);
|
|
}
|
|
|
|
void _genTypeArguments(List<DartType> typeArgs, {Class? instantiatingClass}) {
|
|
int typeArgsCPIndex() {
|
|
if (instantiatingClass != null) {
|
|
return cp.addTypeArguments(
|
|
getInstantiatorTypeArguments(instantiatingClass, typeArgs));
|
|
}
|
|
return cp.addTypeArguments(typeArgs);
|
|
}
|
|
|
|
if (typeArgs.isEmpty || !hasFreeTypeParameters(typeArgs)) {
|
|
// Instantiated type arguments should not depend on
|
|
// the type parameters of the enclosing function.
|
|
objectTable.withoutEnclosingFunctionTypeParameters(() {
|
|
asm.emitPushConstant(typeArgsCPIndex());
|
|
});
|
|
} else {
|
|
final flattenedTypeArgs = (instantiatingClass != null &&
|
|
(instantiatorTypeArguments != null ||
|
|
functionTypeParameters != null))
|
|
? flattenInstantiatorTypeArguments(instantiatingClass, typeArgs)
|
|
: typeArgs;
|
|
if (_canReuseInstantiatorTypeArguments(flattenedTypeArgs)) {
|
|
_genPushInstantiatorTypeArguments();
|
|
} else if (_canReuseFunctionTypeArguments(flattenedTypeArgs)) {
|
|
_genPushFunctionTypeArguments();
|
|
} else {
|
|
_genPushInstantiatorAndFunctionTypeArguments(typeArgs);
|
|
// TODO(alexmarkov): Optimize type arguments instantiation
|
|
// by passing rA = 1 in InstantiateTypeArgumentsTOS.
|
|
// For this purpose, we need to detect if type arguments
|
|
// would be all-dynamic in case of all-dynamic instantiator and
|
|
// function type arguments.
|
|
// Corresponding check is implemented in VM in
|
|
// TypeArguments::IsRawWhenInstantiatedFromRaw.
|
|
asm.emitInstantiateTypeArgumentsTOS(0, typeArgsCPIndex());
|
|
}
|
|
}
|
|
}
|
|
|
|
void _genPushInstantiatorAndFunctionTypeArguments(List<DartType> types) {
|
|
final classTypeParameters = this.classTypeParameters;
|
|
if (classTypeParameters != null &&
|
|
types.any((t) => containsTypeParameter(t, classTypeParameters))) {
|
|
assert(instantiatorTypeArguments != null);
|
|
_genPushInstantiatorTypeArguments();
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
final functionTypeParametersSet = this.functionTypeParametersSet;
|
|
if (functionTypeParametersSet != null &&
|
|
types.any((t) => containsTypeParameter(t, functionTypeParametersSet))) {
|
|
_genPushFunctionTypeArguments();
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
}
|
|
|
|
void _genPushInstantiatorTypeArguments() {
|
|
if (instantiatorTypeArguments != null) {
|
|
if (locals.hasFactoryTypeArgsVar) {
|
|
assert(enclosingMember is Procedure &&
|
|
(enclosingMember as Procedure).isFactory);
|
|
_genLoadVar(locals.factoryTypeArgsVar);
|
|
} else {
|
|
_genPushReceiver();
|
|
final int cpIndex = cp.addTypeArgumentsField(enclosingClass!);
|
|
asm.emitLoadTypeArgumentsField(cpIndex);
|
|
}
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
}
|
|
|
|
bool _canReuseInstantiatorTypeArguments(List<DartType> typeArgs) {
|
|
final instantiatorTypeArguments = this.instantiatorTypeArguments;
|
|
if (instantiatorTypeArguments == null) {
|
|
return false;
|
|
}
|
|
|
|
if (typeArgs.length > instantiatorTypeArguments.length) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < typeArgs.length; ++i) {
|
|
if (typeArgs[i] != instantiatorTypeArguments[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _canReuseFunctionTypeArguments(List<DartType> typeArgs) {
|
|
final functionTypeParameters = this.functionTypeParameters;
|
|
if (functionTypeParameters == null) {
|
|
return false;
|
|
}
|
|
|
|
if (typeArgs.length > functionTypeParameters.length) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < typeArgs.length; ++i) {
|
|
final typeArg = typeArgs[i];
|
|
if (!(typeArg is TypeParameterType &&
|
|
typeArg.parameter == functionTypeParameters[i] &&
|
|
(typeArg.nullability == Nullability.nonNullable ||
|
|
typeArg.nullability == Nullability.undetermined))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void _genPushFunctionTypeArguments() {
|
|
if (locals.hasFunctionTypeArgsVar) {
|
|
asm.emitPush(locals.functionTypeArgsVarIndexInFrame);
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
}
|
|
|
|
void _genPushContextForVariable(VariableDeclaration variable,
|
|
{int? currentContextLevel}) {
|
|
currentContextLevel ??= locals.currentContextLevel;
|
|
int depth = currentContextLevel - locals.getContextLevelOfVar(variable);
|
|
assert(depth >= 0);
|
|
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
if (depth > 0) {
|
|
for (; depth > 0; --depth) {
|
|
asm.emitLoadContextParent();
|
|
}
|
|
}
|
|
}
|
|
|
|
void _genPushContextIfCaptured(VariableDeclaration variable) {
|
|
if (locals.isCaptured(variable)) {
|
|
_genPushContextForVariable(variable);
|
|
}
|
|
}
|
|
|
|
void _genLoadVar(VariableDeclaration v, {int? currentContextLevel}) {
|
|
if (locals.isCaptured(v)) {
|
|
_genPushContextForVariable(v, currentContextLevel: currentContextLevel);
|
|
asm.emitLoadContextVar(
|
|
locals.getVarContextId(v), locals.getVarIndexInContext(v));
|
|
} else {
|
|
asm.emitPush(locals.getVarIndexInFrame(v));
|
|
}
|
|
}
|
|
|
|
void _genPushReceiver() {
|
|
// TODO(alexmarkov): generate more efficient access to receiver
|
|
// even if it is captured.
|
|
_genLoadVar(locals.receiverVar);
|
|
}
|
|
|
|
// Stores value into variable.
|
|
// If variable is captured, context should be pushed before value.
|
|
void _genStoreVar(VariableDeclaration variable) {
|
|
if (locals.isCaptured(variable)) {
|
|
asm.emitStoreContextVar(locals.getVarContextId(variable),
|
|
locals.getVarIndexInContext(variable));
|
|
} else {
|
|
asm.emitPopLocal(locals.getVarIndexInFrame(variable));
|
|
}
|
|
}
|
|
|
|
/// Generates bool condition. Returns `true` if condition is negated.
|
|
bool _genCondition(Expression condition) {
|
|
bool negated = false;
|
|
if (condition is Not) {
|
|
condition = condition.operand;
|
|
negated = true;
|
|
}
|
|
_generateNode(condition);
|
|
return negated;
|
|
}
|
|
|
|
/// Returns value of the given expression if it is a bool constant.
|
|
/// Otherwise, returns `null`.
|
|
bool? _constantConditionValue(Expression condition) {
|
|
if (options.keepUnreachableCode) {
|
|
return null;
|
|
}
|
|
// TODO(dartbug.com/34585): use constant evaluator to evaluate
|
|
// expressions in a non-constant context.
|
|
if (condition is Not) {
|
|
final operand = _constantConditionValue(condition.operand);
|
|
return (operand != null) ? !operand : null;
|
|
}
|
|
if (condition is BoolLiteral) {
|
|
return condition.value;
|
|
}
|
|
if (condition is ConstantExpression) {
|
|
Constant constant = condition.constant;
|
|
if (constant is BoolConstant) {
|
|
return constant.value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _genConditionAndJumpIf(Expression condition, bool value, Label dest) {
|
|
final bool? constantValue = _constantConditionValue(condition);
|
|
if (constantValue != null) {
|
|
if (constantValue == value) {
|
|
asm.emitJump(dest);
|
|
}
|
|
return;
|
|
}
|
|
if (condition is EqualsNull) {
|
|
_generateNode(condition.expression);
|
|
if (options.emitDebuggerStops &&
|
|
condition.fileOffset != TreeNode.noOffset) {
|
|
final savedSourcePosition = asm.currentSourcePosition;
|
|
_recordSourcePosition(condition.fileOffset);
|
|
asm.emitDebugCheck();
|
|
asm.currentSourcePosition = savedSourcePosition;
|
|
}
|
|
if (value) {
|
|
asm.emitJumpIfNull(dest);
|
|
} else {
|
|
asm.emitJumpIfNotNull(dest);
|
|
}
|
|
return;
|
|
}
|
|
if (condition is Not) {
|
|
_genConditionAndJumpIf(condition.operand, !value, dest);
|
|
} else if (condition is LogicalExpression) {
|
|
final isOR = (condition.operatorEnum == LogicalExpressionOperator.OR);
|
|
|
|
Label shortCircuit;
|
|
Label? done;
|
|
if (isOR == value) {
|
|
shortCircuit = dest;
|
|
} else {
|
|
shortCircuit = done = new Label();
|
|
}
|
|
_genConditionAndJumpIf(condition.left, isOR, shortCircuit);
|
|
_genConditionAndJumpIf(condition.right, value, dest);
|
|
if (done != null) {
|
|
asm.bind(done);
|
|
}
|
|
} else {
|
|
bool negated = _genCondition(condition);
|
|
if (negated) {
|
|
value = !value;
|
|
}
|
|
if (value) {
|
|
asm.emitJumpIfTrue(dest);
|
|
} else {
|
|
asm.emitJumpIfFalse(dest);
|
|
}
|
|
}
|
|
}
|
|
|
|
int _getDefaultParamConstIndex(VariableDeclaration param) {
|
|
final paramInitializer = param.initializer;
|
|
if (paramInitializer == null) {
|
|
return cp.addObjectRef(null);
|
|
}
|
|
final constant = _getConstant(paramInitializer);
|
|
return cp.addObjectRef(constant);
|
|
}
|
|
|
|
// Duplicates value on top of the stack using temporary variable with
|
|
// given index.
|
|
void _genDupTOS(int tempIndexInFrame) {
|
|
// TODO(alexmarkov): Consider introducing Dup bytecode or keeping track of
|
|
// expression stack depth.
|
|
asm.emitStoreLocal(tempIndexInFrame);
|
|
asm.emitPush(tempIndexInFrame);
|
|
}
|
|
|
|
/// Generates is-test for the value at TOS.
|
|
void _genInstanceOf(DartType type) {
|
|
if (_isTopType(type)) {
|
|
asm.emitDrop1();
|
|
asm.emitPushTrue();
|
|
return;
|
|
}
|
|
|
|
if (type is InterfaceType &&
|
|
(type.typeArguments.isEmpty || isAllDynamic(type.typeArguments))) {
|
|
asm.emitPushConstant(cp.addType(type));
|
|
final argDesc = objectTable.getArgDescHandle(2);
|
|
final cpIndex = cp.addInterfaceCall(
|
|
InvocationKind.method, objectSimpleInstanceOf, argDesc);
|
|
asm.emitInterfaceCall(cpIndex, 2);
|
|
return;
|
|
}
|
|
|
|
if (hasFreeTypeParameters([type])) {
|
|
_genPushInstantiatorAndFunctionTypeArguments([type]);
|
|
asm.emitPushConstant(cp.addType(type));
|
|
} else {
|
|
asm.emitPushNull(); // Instantiator type arguments.
|
|
asm.emitPushNull(); // Function type arguments.
|
|
// Instantiated type should not depend on
|
|
// the type parameters of the enclosing function.
|
|
objectTable.withoutEnclosingFunctionTypeParameters(() {
|
|
asm.emitPushConstant(cp.addType(type));
|
|
});
|
|
}
|
|
final argDesc = objectTable.getArgDescHandle(4);
|
|
final cpIndex =
|
|
cp.addInterfaceCall(InvocationKind.method, objectInstanceOf, argDesc);
|
|
asm.emitInterfaceCall(cpIndex, 4);
|
|
}
|
|
|
|
void start(Member node, bool hasCode) {
|
|
final enclosingClass = this.enclosingClass = node.enclosingClass;
|
|
enclosingMember = node;
|
|
final enclosingFunction = this.enclosingFunction = node.function;
|
|
parentFunction = null;
|
|
isClosure = false;
|
|
hasErrors = false;
|
|
staticTypeContext.enterMember(node);
|
|
final isFactory = node is Procedure && node.isFactory;
|
|
if (node.isInstanceMember || node is Constructor || isFactory) {
|
|
if (enclosingClass!.typeParameters.isNotEmpty) {
|
|
final classTypeParameters = this.classTypeParameters =
|
|
new Set<TypeParameter>.from(enclosingClass.typeParameters);
|
|
// Treat type arguments of factory constructors as class
|
|
// type parameters.
|
|
if (isFactory) {
|
|
classTypeParameters.addAll(node.function.typeParameters);
|
|
}
|
|
}
|
|
if (hasInstantiatorTypeArguments(enclosingClass)) {
|
|
final typeParameters = getTypeParameterTypes(isFactory
|
|
? node.function.typeParameters
|
|
: enclosingClass.typeParameters);
|
|
instantiatorTypeArguments =
|
|
flattenInstantiatorTypeArguments(enclosingClass, typeParameters);
|
|
}
|
|
}
|
|
if (enclosingFunction != null &&
|
|
enclosingFunction.typeParameters.isNotEmpty) {
|
|
final functionTypeParameters = this.functionTypeParameters =
|
|
new List<TypeParameter>.from(enclosingFunction.typeParameters);
|
|
functionTypeParametersSet = functionTypeParameters.toSet();
|
|
objectTable.numEnclosingFunctionTypeParameters =
|
|
functionTypeParameters.length;
|
|
}
|
|
|
|
if (!hasCode) {
|
|
return;
|
|
}
|
|
|
|
labeledStatements = null;
|
|
switchCases = null;
|
|
tryCatches = null;
|
|
finallyBlocks = null;
|
|
asyncTryBlock = null;
|
|
contextLevels = null;
|
|
closures = null;
|
|
initializedFields = const {}; // Tracked for constructors only.
|
|
nullableFields = const [];
|
|
cp = new ConstantPool(stringTable, objectTable);
|
|
asm = new BytecodeAssembler(options);
|
|
savedAssemblers = null;
|
|
currentLoopDepth = 0;
|
|
savedMaxSourcePositions = <int>[];
|
|
maxSourcePosition = node.fileOffset;
|
|
|
|
locals = new LocalVariables(node, options, staticTypeContext);
|
|
locals.enterScope(node);
|
|
|
|
int position;
|
|
if (node is Procedure) {
|
|
position = node.fileStartOffset;
|
|
} else if (node is Constructor) {
|
|
position = node.startFileOffset;
|
|
} else {
|
|
position = node.fileOffset;
|
|
}
|
|
_recordSourcePosition(position);
|
|
_genPrologue(node, node.function);
|
|
_setupInitialContext(node.function);
|
|
_emitFirstDebugCheck(node.function);
|
|
_genEqualsOperatorNullHandling(node);
|
|
if (node is Procedure && node.isInstanceMember) {
|
|
_checkArguments(node.function);
|
|
}
|
|
_initSuspendableFunction(node.function);
|
|
}
|
|
|
|
// Generate additional code for 'operator ==' to handle nulls.
|
|
void _genEqualsOperatorNullHandling(Member member) {
|
|
if (member.name.text != '==' ||
|
|
locals.numParameters != 2 ||
|
|
member.enclosingClass == coreTypes.objectClass) {
|
|
return;
|
|
}
|
|
|
|
Label done = new Label();
|
|
|
|
_genLoadVar(member.function!.positionalParameters[0]);
|
|
asm.emitJumpIfNotNull(done);
|
|
|
|
asm.emitPushFalse();
|
|
_genReturnTOS();
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
void _initSuspendableFunction(FunctionNode? function) {
|
|
if (!locals.isSuspendableFunction) {
|
|
return;
|
|
}
|
|
Procedure initMethod;
|
|
switch (function!.dartAsyncMarker) {
|
|
case AsyncMarker.Async:
|
|
initMethod = initAsync;
|
|
break;
|
|
case AsyncMarker.AsyncStar:
|
|
initMethod = initAsyncStar;
|
|
break;
|
|
case AsyncMarker.SyncStar:
|
|
initMethod = initSyncStar;
|
|
break;
|
|
default:
|
|
throw 'Unexpected async marker ${function.dartAsyncMarker}';
|
|
}
|
|
_genTypeArguments([function.emittedValueType!]);
|
|
_genDirectCall(initMethod, objectTable.getArgDescHandle(0, 1), 1);
|
|
asm.emitPopLocal(locals.suspendStateVarIndexInFrame);
|
|
|
|
if (function.dartAsyncMarker != AsyncMarker.Async) {
|
|
// Suspend async* and sync* functions after prologue is finished.
|
|
Label done = Label();
|
|
asm.emitSuspend(done);
|
|
|
|
final suspendMethod = (function.dartAsyncMarker == AsyncMarker.AsyncStar)
|
|
? yieldAsyncStar
|
|
: suspendSyncStarAtStart;
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPushNull();
|
|
_genDirectCall(suspendMethod, objectTable.getArgDescHandle(2), 2);
|
|
asm.emitReturnTOS();
|
|
|
|
asm.bind(done);
|
|
asm.emitDrop1(); // Discard result of Suspend.
|
|
}
|
|
|
|
if (function.dartAsyncMarker == AsyncMarker.SyncStar &&
|
|
locals.currentContextSize > 0) {
|
|
// Clone context if there are any captured parameter variables, so
|
|
// each invocation of .iterator would get its own copy of parameters.
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
asm.emitCloneContext(locals.currentContextId, locals.currentContextSize);
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
}
|
|
|
|
if (function.dartAsyncMarker == AsyncMarker.Async ||
|
|
function.dartAsyncMarker == AsyncMarker.AsyncStar) {
|
|
final asyncTryBlock =
|
|
this.asyncTryBlock = asm.exceptionsTable.enterTryBlock(asm.offset);
|
|
asyncTryBlock.isSynthetic = true;
|
|
asyncTryBlock.needsStackTrace = true;
|
|
asyncTryBlock.types.add(cp.addType(const DynamicType()));
|
|
}
|
|
}
|
|
|
|
void _endSuspendableFunction(FunctionNode? function) {
|
|
if (!locals.isSuspendableFunction) {
|
|
return;
|
|
}
|
|
if (function!.dartAsyncMarker == AsyncMarker.Async ||
|
|
function.dartAsyncMarker == AsyncMarker.AsyncStar) {
|
|
final asyncTryBlock = this.asyncTryBlock!;
|
|
asyncTryBlock.endPC = asm.offset;
|
|
asyncTryBlock.handlerPC = asm.offset;
|
|
|
|
// Exception handlers are reachable although there are no labels or jumps.
|
|
asm.isUnreachable = false;
|
|
|
|
asm.emitSetFrame(locals.frameSize);
|
|
|
|
final rethrowException = Label();
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitJumpIfNull(rethrowException);
|
|
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
final int temp = locals.suspendStateVarIndexInFrame;
|
|
asm.emitMoveSpecial(SpecialIndex.exception, temp);
|
|
asm.emitPush(temp);
|
|
asm.emitMoveSpecial(SpecialIndex.stackTrace, temp);
|
|
asm.emitPush(temp);
|
|
_genDirectCall(handleException, objectTable.getArgDescHandle(3), 3);
|
|
asm.emitReturnTOS();
|
|
|
|
asm.bind(rethrowException);
|
|
asm.emitMoveSpecial(SpecialIndex.exception, temp);
|
|
asm.emitPush(temp);
|
|
asm.emitMoveSpecial(SpecialIndex.stackTrace, temp);
|
|
asm.emitPush(temp);
|
|
asm.emitThrow(1);
|
|
}
|
|
}
|
|
|
|
void end(Member node, bool hasCode) {
|
|
if (!hasErrors) {
|
|
Code? code;
|
|
if (hasCode) {
|
|
_endSuspendableFunction(node.function);
|
|
|
|
if (options.emitLocalVarInfo) {
|
|
// Leave the scopes which were entered in _genPrologue and
|
|
// _setupInitialContext.
|
|
asm.localVariableTable.leaveAllScopes(
|
|
asm.offset, node.function?.fileEndOffset ?? node.fileEndOffset);
|
|
}
|
|
|
|
List<int>? parameterFlags = null;
|
|
int? forwardingStubTargetCpIndex = null;
|
|
int? defaultFunctionTypeArgsCpIndex = null;
|
|
|
|
if (node is Constructor) {
|
|
parameterFlags =
|
|
ParameterFlags.getFunctionFlags(node.function, isCode: true);
|
|
} else if (node is Procedure) {
|
|
parameterFlags =
|
|
ParameterFlags.getFunctionFlags(node.function, isCode: true);
|
|
|
|
if (node.isForwardingStub) {
|
|
forwardingStubTargetCpIndex = cp.addObjectRef(node.stubTarget);
|
|
}
|
|
|
|
final defaultTypes = getDefaultFunctionTypeArguments(node.function);
|
|
if (defaultTypes != null) {
|
|
defaultFunctionTypeArgsCpIndex = cp.addTypeArguments(defaultTypes);
|
|
}
|
|
}
|
|
code = new Code(
|
|
cp,
|
|
asm.bytecode,
|
|
asm.exceptionsTable,
|
|
finalizeSourcePositions(),
|
|
finalizeLocalVariables(),
|
|
nullableFields,
|
|
closures ?? const <ClosureDeclaration>[],
|
|
parameterFlags,
|
|
forwardingStubTargetCpIndex,
|
|
defaultFunctionTypeArgsCpIndex);
|
|
bytecodeComponent.codes.add(code);
|
|
}
|
|
if (node is Field) {
|
|
fieldDeclarations.add(getFieldDeclaration(node, code));
|
|
} else {
|
|
functionDeclarations.add(getFunctionDeclaration(node, code));
|
|
}
|
|
}
|
|
|
|
objectTable.numEnclosingFunctionTypeParameters = 0;
|
|
staticTypeContext.leaveMember(node);
|
|
enclosingClass = null;
|
|
enclosingMember = null;
|
|
enclosingFunction = null;
|
|
parentFunction = null;
|
|
isClosure = false;
|
|
classTypeParameters = null;
|
|
functionTypeParameters = null;
|
|
functionTypeParametersSet = null;
|
|
instantiatorTypeArguments = null;
|
|
labeledStatements = null;
|
|
switchCases = null;
|
|
tryCatches = null;
|
|
finallyBlocks = null;
|
|
asyncTryBlock = null;
|
|
contextLevels = null;
|
|
closures = null;
|
|
initializedFields = const {};
|
|
nullableFields = const [];
|
|
savedAssemblers = null;
|
|
hasErrors = false;
|
|
}
|
|
|
|
SourcePositions? finalizeSourcePositions() {
|
|
if (asm.sourcePositions.isEmpty) {
|
|
return null;
|
|
}
|
|
bytecodeComponent.sourcePositions.add(asm.sourcePositions);
|
|
return asm.sourcePositions;
|
|
}
|
|
|
|
LocalVariableTable? finalizeLocalVariables() {
|
|
final localVariables = asm.localVariableTable;
|
|
assert(!localVariables.hasActiveScopes);
|
|
if (localVariables.isEmpty) {
|
|
return null;
|
|
}
|
|
bytecodeComponent.localVariables.add(localVariables);
|
|
return localVariables;
|
|
}
|
|
|
|
void _genPrologue(Node node, FunctionNode? function) {
|
|
if (locals.makesCopyOfParameters) {
|
|
final int numOptionalPositional = function!.positionalParameters.length -
|
|
function.requiredParameterCount;
|
|
final int numOptionalNamed = function.namedParameters.length;
|
|
final int numFixed =
|
|
locals.numParameters - (numOptionalPositional + numOptionalNamed);
|
|
|
|
if (locals.isSuspendableFunction) {
|
|
asm.emitEntrySuspendable(
|
|
numFixed, numOptionalPositional, numOptionalNamed);
|
|
} else {
|
|
asm.emitEntryOptional(
|
|
numFixed, numOptionalPositional, numOptionalNamed);
|
|
}
|
|
|
|
if (numOptionalPositional != 0) {
|
|
assert(numOptionalNamed == 0);
|
|
for (int i = 0; i < numOptionalPositional; i++) {
|
|
final param = function
|
|
.positionalParameters[function.requiredParameterCount + i];
|
|
final localIndex = locals.getParamIndexInFrame(param);
|
|
asm.emitLoadConstant(localIndex, _getDefaultParamConstIndex(param));
|
|
}
|
|
} else {
|
|
for (int i = 0; i < numOptionalNamed; i++) {
|
|
final param = locals.sortedNamedParameters[i];
|
|
final localIndex = locals.getParamIndexInFrame(param);
|
|
asm.emitLoadConstant(localIndex, cp.addName(param.name!));
|
|
asm.emitLoadConstant(localIndex, _getDefaultParamConstIndex(param));
|
|
}
|
|
}
|
|
|
|
asm.emitFrame(locals.frameSize - locals.numParameters);
|
|
} else {
|
|
asm.emitEntry(locals.frameSize);
|
|
}
|
|
|
|
if (isClosure) {
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureContext));
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
}
|
|
|
|
if (locals.hasFunctionTypeArgsVar && function!.typeParameters.isNotEmpty) {
|
|
assert(!(node is Procedure && node.isFactory));
|
|
|
|
Label done = new Label();
|
|
|
|
if (isClosure) {
|
|
_handleDelayedTypeArguments(done);
|
|
}
|
|
|
|
asm.emitCheckFunctionTypeArgs(function.typeParameters.length,
|
|
locals.functionTypeArgsVarIndexInFrame);
|
|
|
|
_handleDefaultTypeArguments(function, done);
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
// Open initial scope before the first CheckStack, as VM might
|
|
// need to know context level.
|
|
if (options.emitLocalVarInfo && function != null) {
|
|
asm.localVariableTable.enterScope(
|
|
asm.offset,
|
|
isClosure ? locals.contextLevelAtEntry : locals.currentContextLevel,
|
|
function.fileOffset);
|
|
if (locals.hasContextVar) {
|
|
asm.localVariableTable
|
|
.recordContextVariable(asm.offset, locals.contextVarIndexInFrame);
|
|
}
|
|
if (locals.hasReceiver &&
|
|
(!isClosure || locals.isCaptured(locals.receiverVar))) {
|
|
_declareLocalVariable(locals.receiverVar, function.fileOffset);
|
|
}
|
|
for (var v in function.positionalParameters) {
|
|
if (!locals.isCaptured(v)) {
|
|
_declareLocalVariable(v, function.fileOffset);
|
|
}
|
|
}
|
|
for (var v in locals.sortedNamedParameters) {
|
|
if (!locals.isCaptured(v)) {
|
|
_declareLocalVariable(v, function.fileOffset);
|
|
}
|
|
}
|
|
if (locals.hasFunctionTypeArgsVar) {
|
|
_declareLocalVariable(locals.functionTypeArgsVar, function.fileOffset);
|
|
}
|
|
}
|
|
|
|
// CheckStack must see a properly initialized context when stress-testing
|
|
// stack trace collection.
|
|
asm.emitCheckStack(0);
|
|
|
|
if (locals.hasFunctionTypeArgsVar && isClosure) {
|
|
if (function!.typeParameters.isNotEmpty) {
|
|
final int numParentTypeArgs = locals.numParentTypeArguments;
|
|
asm.emitPush(locals.functionTypeArgsVarIndexInFrame);
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureFunctionTypeArguments));
|
|
_genPushInt(numParentTypeArgs);
|
|
_genPushInt(numParentTypeArgs + function.typeParameters.length);
|
|
_genDirectCall(
|
|
prependTypeArguments, objectTable.getArgDescHandle(4), 4);
|
|
asm.emitPopLocal(locals.functionTypeArgsVarIndexInFrame);
|
|
} else {
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureFunctionTypeArguments));
|
|
asm.emitPopLocal(locals.functionTypeArgsVarIndexInFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _handleDelayedTypeArguments(Label doneCheckingTypeArguments) {
|
|
Label noDelayedTypeArgs = new Label();
|
|
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureDelayedTypeArguments));
|
|
asm.emitStoreLocal(locals.functionTypeArgsVarIndexInFrame);
|
|
asm.emitPushConstant(cp.addEmptyTypeArguments());
|
|
asm.emitJumpIfEqStrict(noDelayedTypeArgs);
|
|
|
|
// There are non-empty delayed type arguments, and they are stored
|
|
// into function type args variable already.
|
|
// Just verify that there are no passed type arguments.
|
|
asm.emitCheckFunctionTypeArgs(0, locals.scratchVarIndexInFrame);
|
|
asm.emitJump(doneCheckingTypeArguments);
|
|
|
|
asm.bind(noDelayedTypeArgs);
|
|
}
|
|
|
|
void _handleDefaultTypeArguments(
|
|
FunctionNode function, Label doneCheckingTypeArguments) {
|
|
List<DartType>? defaultTypes = getDefaultFunctionTypeArguments(function);
|
|
if (defaultTypes == null) {
|
|
return;
|
|
}
|
|
|
|
asm.emitJumpIfNotZeroTypeArgs(doneCheckingTypeArguments);
|
|
|
|
// Load parent function type arguments if they are used to
|
|
// instantiate default types.
|
|
if (isClosure &&
|
|
defaultTypes
|
|
.any((t) => containsTypeParameter(t, functionTypeParametersSet!))) {
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureFunctionTypeArguments));
|
|
asm.emitPopLocal(locals.functionTypeArgsVarIndexInFrame);
|
|
}
|
|
|
|
_genTypeArguments(defaultTypes);
|
|
asm.emitPopLocal(locals.functionTypeArgsVarIndexInFrame);
|
|
}
|
|
|
|
void _setupInitialContext(FunctionNode? function) {
|
|
_allocateContextIfNeeded();
|
|
|
|
if (options.emitLocalVarInfo && locals.currentContextSize > 0) {
|
|
// Open a new scope after allocating context.
|
|
asm.localVariableTable.enterScope(asm.offset, locals.currentContextLevel,
|
|
function?.fileOffset ?? enclosingMember!.fileOffset);
|
|
}
|
|
|
|
if (locals.hasCapturedParameters) {
|
|
// Copy captured parameters to their respective locations in the context.
|
|
if (!isClosure) {
|
|
if (locals.hasFactoryTypeArgsVar) {
|
|
_copyParamIfCaptured(locals.factoryTypeArgsVar);
|
|
}
|
|
if (locals.hasCapturedReceiverVar) {
|
|
_genPushContextForVariable(locals.capturedReceiverVar);
|
|
asm.emitPush(locals.getVarIndexInFrame(locals.receiverVar));
|
|
_genStoreVar(locals.capturedReceiverVar);
|
|
}
|
|
}
|
|
if (function != null) {
|
|
function.positionalParameters.forEach(_copyParamIfCaptured);
|
|
locals.sortedNamedParameters.forEach(_copyParamIfCaptured);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _emitFirstDebugCheck(FunctionNode? function) {
|
|
if (options.emitDebuggerStops) {
|
|
// DebugCheck instruction should be emitted after parameter variables
|
|
// are declared and copied into context.
|
|
// The debugger expects the source position to correspond to the
|
|
// declaration position of the last parameter, if any, or of the function.
|
|
// The DebugCheck must be encountered each time an async op is reentered.
|
|
if (options.emitSourcePositions && function != null) {
|
|
var pos = TreeNode.noOffset;
|
|
if (function.namedParameters.isNotEmpty) {
|
|
pos = function.namedParameters.last.fileOffset;
|
|
} else if (function.positionalParameters.isNotEmpty) {
|
|
pos = function.positionalParameters.last.fileOffset;
|
|
}
|
|
if (pos == TreeNode.noOffset) {
|
|
pos = function.fileOffset;
|
|
}
|
|
_recordSourcePosition(pos);
|
|
}
|
|
asm.emitDebugCheck();
|
|
}
|
|
}
|
|
|
|
void _copyParamIfCaptured(VariableDeclaration variable) {
|
|
if (locals.isCaptured(variable)) {
|
|
if (options.emitLocalVarInfo) {
|
|
_declareLocalVariable(variable, enclosingFunction!.fileOffset);
|
|
}
|
|
_genPushContextForVariable(variable);
|
|
asm.emitPush(locals.getParamIndexInFrame(variable));
|
|
_genStoreVar(variable);
|
|
// TODO(alexmarkov): We need to store null at the original parameter
|
|
// location, because the original value may need to be GC'ed.
|
|
}
|
|
}
|
|
|
|
void _declareLocalVariable(
|
|
VariableDeclaration variable, int initializedPosition) {
|
|
bool isCaptured = locals.isCaptured(variable);
|
|
asm.localVariableTable.declareVariable(
|
|
asm.offset,
|
|
isCaptured,
|
|
isCaptured
|
|
? locals.getVarIndexInContext(variable)
|
|
: locals.getVarIndexInFrame(variable),
|
|
cp.addName(variable.name!),
|
|
cp.addType(variable.type),
|
|
variable.fileOffset,
|
|
initializedPosition);
|
|
}
|
|
|
|
// TODO(dartbug.com/40813): Remove the closure case when we move the
|
|
// type checks out of closure bodies.
|
|
bool get canSkipTypeChecksForNonCovariantArguments => !isClosure;
|
|
|
|
Member? _getForwardingStubSuperTarget() {
|
|
if (!isClosure) {
|
|
final member = enclosingMember!;
|
|
if (member.isInstanceMember &&
|
|
member is Procedure &&
|
|
member.isForwardingStub) {
|
|
return member.stubTarget;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Types in a target of a forwarding stub are encoded in terms of target type
|
|
// parameters. Substitute them with host type parameters to be able
|
|
// to use them (e.g. instantiate) in the context of host.
|
|
Substitution? _getForwardingSubstitution(
|
|
FunctionNode host, Member? forwardingTarget) {
|
|
if (forwardingTarget == null) {
|
|
return null;
|
|
}
|
|
final Class targetClass = forwardingTarget.enclosingClass!;
|
|
final Supertype? instantiatedTargetClass =
|
|
hierarchy.getClassAsInstanceOf(enclosingClass!, targetClass);
|
|
if (instantiatedTargetClass == null) {
|
|
throw 'Class $targetClass is not found among implemented interfaces of'
|
|
' $enclosingClass (for forwarding stub $enclosingMember)';
|
|
}
|
|
assert(instantiatedTargetClass.classNode == targetClass);
|
|
assert(instantiatedTargetClass.typeArguments.length ==
|
|
targetClass.typeParameters.length);
|
|
final Map<TypeParameter, DartType> map =
|
|
new Map<TypeParameter, DartType>.fromIterables(
|
|
targetClass.typeParameters, instantiatedTargetClass.typeArguments);
|
|
if (forwardingTarget.function != null) {
|
|
final targetTypeParameters = forwardingTarget.function!.typeParameters;
|
|
assert(host.typeParameters.length == targetTypeParameters.length);
|
|
for (int i = 0; i < targetTypeParameters.length; ++i) {
|
|
map[targetTypeParameters[i]] = new TypeParameterType(
|
|
host.typeParameters[i],
|
|
host.typeParameters[i].computeNullabilityFromBound());
|
|
}
|
|
}
|
|
return Substitution.fromMap(map);
|
|
}
|
|
|
|
/// If member being compiled is a forwarding stub, then returns type
|
|
/// parameter bounds to check for the forwarding stub target.
|
|
Map<TypeParameter, DartType>? _getForwardingBounds(FunctionNode function,
|
|
Member? forwardingTarget, Substitution? forwardingSubstitution) {
|
|
if (function.typeParameters.isEmpty || forwardingTarget == null) {
|
|
return null;
|
|
}
|
|
final forwardingBounds = <TypeParameter, DartType>{};
|
|
for (int i = 0; i < function.typeParameters.length; ++i) {
|
|
DartType bound = forwardingSubstitution!
|
|
.substituteType(forwardingTarget.function!.typeParameters[i].bound);
|
|
forwardingBounds[function.typeParameters[i]] = bound;
|
|
}
|
|
return forwardingBounds;
|
|
}
|
|
|
|
/// If member being compiled is a forwarding stub, then returns parameter
|
|
/// types to check for the forwarding stub target.
|
|
Map<VariableDeclaration, DartType>? _getForwardingParameterTypes(
|
|
FunctionNode function,
|
|
Member? forwardingTarget,
|
|
Substitution? forwardingSubstitution) {
|
|
if (forwardingTarget == null) {
|
|
return null;
|
|
}
|
|
|
|
if (forwardingTarget is Field) {
|
|
if ((enclosingMember as Procedure).isGetter) {
|
|
return const <VariableDeclaration, DartType>{};
|
|
} else {
|
|
// Forwarding stub for a covariant field setter.
|
|
assert((enclosingMember as Procedure).isSetter);
|
|
assert(function.typeParameters.isEmpty &&
|
|
function.positionalParameters.length == 1 &&
|
|
function.namedParameters.isEmpty);
|
|
return <VariableDeclaration, DartType>{
|
|
function.positionalParameters.single:
|
|
forwardingSubstitution!.substituteType(forwardingTarget.type)
|
|
};
|
|
}
|
|
}
|
|
|
|
final forwardingParams = <VariableDeclaration, DartType>{};
|
|
for (int i = 0; i < function.positionalParameters.length; ++i) {
|
|
DartType type = forwardingSubstitution!.substituteType(
|
|
forwardingTarget.function!.positionalParameters[i].type);
|
|
forwardingParams[function.positionalParameters[i]] = type;
|
|
}
|
|
for (var hostParam in function.namedParameters) {
|
|
VariableDeclaration targetParam = forwardingTarget
|
|
.function!.namedParameters
|
|
.firstWhere((p) => p.name == hostParam.name);
|
|
forwardingParams[hostParam] =
|
|
forwardingSubstitution!.substituteType(targetParam.type);
|
|
}
|
|
return forwardingParams;
|
|
}
|
|
|
|
void _checkArguments(FunctionNode function) {
|
|
// When checking arguments of a forwarding stub, we need to use parameter
|
|
// types (and bounds of type parameters) from stub's target.
|
|
// These more accurate type checks is the sole purpose of a forwarding stub.
|
|
final forwardingTarget = _getForwardingStubSuperTarget();
|
|
final forwardingSubstitution =
|
|
_getForwardingSubstitution(function, forwardingTarget);
|
|
final forwardingBounds = _getForwardingBounds(
|
|
function, forwardingTarget, forwardingSubstitution);
|
|
final forwardingParamTypes = _getForwardingParameterTypes(
|
|
function, forwardingTarget, forwardingSubstitution);
|
|
|
|
if (_hasSkippableTypeChecks(
|
|
function, forwardingBounds, forwardingParamTypes)) {
|
|
final Label skipChecks = new Label();
|
|
asm.emitJumpIfUnchecked(skipChecks);
|
|
|
|
// We can skip bounds checks of type parameter and type checks of
|
|
// non-covariant parameters if function is called via unchecked call.
|
|
|
|
for (var typeParam in function.typeParameters) {
|
|
if (_typeParameterNeedsBoundCheck(typeParam, forwardingBounds)) {
|
|
_genTypeParameterBoundCheck(typeParam, forwardingBounds);
|
|
}
|
|
}
|
|
for (var param in function.positionalParameters) {
|
|
if (!param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
_genArgumentTypeCheck(param, forwardingParamTypes);
|
|
}
|
|
}
|
|
for (var param in locals.sortedNamedParameters) {
|
|
if (!param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
_genArgumentTypeCheck(param, forwardingParamTypes);
|
|
}
|
|
}
|
|
|
|
asm.bind(skipChecks);
|
|
}
|
|
|
|
// Covariant parameters need to be checked even if function is called
|
|
// via unchecked call, so they are generated outside of JumpIfUnchecked.
|
|
|
|
for (var param in function.positionalParameters) {
|
|
if (param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
_genArgumentTypeCheck(param, forwardingParamTypes);
|
|
}
|
|
}
|
|
for (var param in locals.sortedNamedParameters) {
|
|
if (param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
_genArgumentTypeCheck(param, forwardingParamTypes);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns true if bound of [typeParam] should be checked.
|
|
bool _typeParameterNeedsBoundCheck(TypeParameter typeParam,
|
|
Map<TypeParameter, DartType>? forwardingTypeParameterBounds) {
|
|
if (canSkipTypeChecksForNonCovariantArguments &&
|
|
!typeParam.isCovariantByClass) {
|
|
return false;
|
|
}
|
|
final DartType bound = (forwardingTypeParameterBounds != null)
|
|
? forwardingTypeParameterBounds[typeParam]!
|
|
: typeParam.bound;
|
|
if (_isTopType(bound)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns true if type of [param] should be checked.
|
|
bool _parameterNeedsTypeCheck(VariableDeclaration param,
|
|
Map<VariableDeclaration, DartType>? forwardingParameterTypes) {
|
|
if (canSkipTypeChecksForNonCovariantArguments &&
|
|
!param.isCovariantByDeclaration &&
|
|
!param.isCovariantByClass) {
|
|
return false;
|
|
}
|
|
final DartType type = (forwardingParameterTypes != null)
|
|
? forwardingParameterTypes[param]!
|
|
: param.type;
|
|
if (_isTopType(type)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Returns true if there are parameter type/bound checks which can
|
|
/// be skipped on unchecked call.
|
|
bool _hasSkippableTypeChecks(
|
|
FunctionNode function,
|
|
Map<TypeParameter, DartType>? forwardingBounds,
|
|
Map<VariableDeclaration, DartType>? forwardingParamTypes) {
|
|
for (var typeParam in function.typeParameters) {
|
|
if (_typeParameterNeedsBoundCheck(typeParam, forwardingBounds)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (var param in function.positionalParameters) {
|
|
if (!param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (var param in locals.sortedNamedParameters) {
|
|
if (!param.isCovariantByDeclaration &&
|
|
_parameterNeedsTypeCheck(param, forwardingParamTypes)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void _genTypeParameterBoundCheck(TypeParameter typeParam,
|
|
Map<TypeParameter, DartType>? forwardingTypeParameterBounds) {
|
|
final DartType bound = (forwardingTypeParameterBounds != null)
|
|
? forwardingTypeParameterBounds[typeParam]!
|
|
: typeParam.bound;
|
|
final DartType type = new TypeParameterType(
|
|
typeParam, typeParam.computeNullabilityFromBound());
|
|
_genPushInstantiatorAndFunctionTypeArguments([type, bound]);
|
|
asm.emitPushConstant(cp.addType(type));
|
|
asm.emitPushConstant(cp.addType(bound));
|
|
asm.emitPushConstant(cp.addName(typeParam.name!));
|
|
asm.emitAssertSubtype();
|
|
}
|
|
|
|
bool _isTopType(DartType type) => switch (type) {
|
|
DynamicType() => true,
|
|
VoidType() => true,
|
|
InterfaceType() => type.classNode == coreTypes.objectClass &&
|
|
type.nullability == Nullability.nullable,
|
|
FutureOrType() => _isTopType(type.typeArgument),
|
|
ExtensionType() => _isTopType(type.extensionTypeErasure),
|
|
_ => false,
|
|
};
|
|
|
|
void _genArgumentTypeCheck(VariableDeclaration variable,
|
|
Map<VariableDeclaration, DartType>? forwardingParameterTypes) {
|
|
final DartType type = (forwardingParameterTypes != null)
|
|
? forwardingParameterTypes[variable]!
|
|
: variable.type;
|
|
asm.emitPush(locals.getParamIndexInFrame(variable));
|
|
_genAssertAssignable(type, name: variable.name);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
void _genAssertAssignable(DartType type, {String? name, String? message}) {
|
|
assert(!_isTopType(type));
|
|
asm.emitPushConstant(cp.addType(type));
|
|
_genPushInstantiatorAndFunctionTypeArguments([type]);
|
|
asm.emitPushConstant(
|
|
name != null ? cp.addName(name) : cp.addString(message!));
|
|
bool isIntOk = typeEnvironment.isSubtypeOf(
|
|
typeEnvironment.coreTypes.intNonNullableRawType, type);
|
|
int subtypeTestCacheCpIndex = cp.addSubtypeTestCache();
|
|
asm.emitAssertAssignable(isIntOk ? 1 : 0, subtypeTestCacheCpIndex);
|
|
}
|
|
|
|
void _pushAssemblerState() {
|
|
final savedAssemblers = this.savedAssemblers ??= <BytecodeAssembler>[];
|
|
savedAssemblers.add(asm);
|
|
asm = new BytecodeAssembler(options);
|
|
}
|
|
|
|
void _popAssemblerState() {
|
|
asm = savedAssemblers!.removeLast();
|
|
}
|
|
|
|
int _genClosureBytecode(
|
|
LocalFunction node, String name, FunctionNode function) {
|
|
_pushAssemblerState();
|
|
|
|
locals.enterScope(node);
|
|
|
|
final savedParentFunction = parentFunction;
|
|
parentFunction = enclosingFunction;
|
|
final savedIsClosure = isClosure;
|
|
isClosure = true;
|
|
enclosingFunction = function;
|
|
final savedLoopDepth = currentLoopDepth;
|
|
currentLoopDepth = 0;
|
|
final savedAsyncTryBlock = asyncTryBlock;
|
|
asyncTryBlock = null;
|
|
|
|
if (function.typeParameters.isNotEmpty) {
|
|
final functionTypeParameters =
|
|
this.functionTypeParameters ??= <TypeParameter>[];
|
|
functionTypeParameters.addAll(function.typeParameters);
|
|
functionTypeParametersSet = functionTypeParameters.toSet();
|
|
objectTable.numEnclosingFunctionTypeParameters =
|
|
functionTypeParameters.length;
|
|
}
|
|
|
|
final closures = this.closures ??= <ClosureDeclaration>[];
|
|
final int closureIndex = closures.length;
|
|
final closure = getClosureDeclaration(node, function, name, closureIndex,
|
|
savedIsClosure ? parentFunction! : enclosingMember!);
|
|
closures.add(closure);
|
|
|
|
final int closureFunctionIndex = cp.addClosureFunction(closureIndex);
|
|
|
|
_recordSourcePosition(function.fileOffset);
|
|
_genPrologue(node, function);
|
|
|
|
_setupInitialContext(function);
|
|
_emitFirstDebugCheck(function);
|
|
_checkArguments(function);
|
|
_initSuspendableFunction(function);
|
|
|
|
_generateNode(function.body);
|
|
|
|
// BytecodeAssembler eliminates this bytecode if it is unreachable.
|
|
_recordSourcePosition(function.fileEndOffset);
|
|
asm.emitPushNull();
|
|
_genReturnTOS();
|
|
|
|
_endSuspendableFunction(function);
|
|
|
|
if (options.emitLocalVarInfo) {
|
|
// Leave the scopes which were entered in _genPrologue and
|
|
// _setupInitialContext.
|
|
asm.localVariableTable.leaveAllScopes(asm.offset, function.fileEndOffset);
|
|
}
|
|
|
|
cp.addEndClosureFunctionScope();
|
|
|
|
if (function.typeParameters.isNotEmpty) {
|
|
functionTypeParameters!.length -= function.typeParameters.length;
|
|
functionTypeParametersSet = functionTypeParameters!.toSet();
|
|
objectTable.numEnclosingFunctionTypeParameters =
|
|
functionTypeParameters!.length;
|
|
}
|
|
|
|
enclosingFunction = parentFunction;
|
|
parentFunction = savedParentFunction;
|
|
isClosure = savedIsClosure;
|
|
currentLoopDepth = savedLoopDepth;
|
|
asyncTryBlock = savedAsyncTryBlock;
|
|
|
|
locals.leaveScope();
|
|
|
|
closure.code = new ClosureCode(asm.bytecode, asm.exceptionsTable,
|
|
finalizeSourcePositions(), finalizeLocalVariables());
|
|
|
|
_popAssemblerState();
|
|
|
|
return closureFunctionIndex;
|
|
}
|
|
|
|
ClosureDeclaration getClosureDeclaration(LocalFunction node,
|
|
FunctionNode function, String name, int closureIndex, TreeNode parent) {
|
|
objectTable.declareClosure(function, enclosingMember!, closureIndex);
|
|
|
|
int flags = 0;
|
|
int position = TreeNode.noOffset;
|
|
int endPosition = TreeNode.noOffset;
|
|
if (options.emitSourcePositions) {
|
|
position = (node is ast.FunctionDeclaration)
|
|
? node.fileOffset
|
|
: function.fileOffset;
|
|
endPosition = function.fileEndOffset;
|
|
if (position != TreeNode.noOffset) {
|
|
flags |= ClosureDeclaration.hasSourcePositionsFlag;
|
|
}
|
|
}
|
|
|
|
switch (function.dartAsyncMarker) {
|
|
case AsyncMarker.Async:
|
|
flags |= ClosureDeclaration.isAsyncFlag;
|
|
break;
|
|
case AsyncMarker.AsyncStar:
|
|
flags |= ClosureDeclaration.isAsyncStarFlag;
|
|
break;
|
|
case AsyncMarker.SyncStar:
|
|
flags |= ClosureDeclaration.isSyncStarFlag;
|
|
break;
|
|
default:
|
|
flags |= ClosureDeclaration.isDebuggableFlag;
|
|
break;
|
|
}
|
|
|
|
final List<NameAndType> parameters = <NameAndType>[];
|
|
for (var v in function.positionalParameters) {
|
|
parameters.add(new NameAndType(objectTable.getPublicNameHandle(v.name!),
|
|
objectTable.getHandle(v.type)!));
|
|
}
|
|
for (var v in function.namedParameters) {
|
|
parameters.add(new NameAndType(objectTable.getPublicNameHandle(v.name!),
|
|
objectTable.getHandle(v.type)!));
|
|
}
|
|
if (function.requiredParameterCount != parameters.length) {
|
|
if (function.namedParameters.isNotEmpty) {
|
|
flags |= ClosureDeclaration.hasOptionalNamedParamsFlag;
|
|
} else {
|
|
flags |= ClosureDeclaration.hasOptionalPositionalParamsFlag;
|
|
}
|
|
}
|
|
|
|
TypeParametersDeclaration? typeParameters;
|
|
if (function.typeParameters.isNotEmpty) {
|
|
flags |= ClosureDeclaration.hasTypeParamsFlag;
|
|
typeParameters = getTypeParametersDeclaration(function.typeParameters);
|
|
}
|
|
|
|
final parameterFlags =
|
|
ParameterFlags.getFunctionFlags(function, isCode: false);
|
|
if (parameterFlags != null) {
|
|
flags |= ClosureDeclaration.hasParameterFlagsFlag;
|
|
}
|
|
|
|
return new ClosureDeclaration(
|
|
flags,
|
|
objectTable.getHandle(parent)!,
|
|
objectTable.getPublicNameHandle(name),
|
|
position,
|
|
endPosition,
|
|
typeParameters,
|
|
function.requiredParameterCount,
|
|
function.namedParameters.length,
|
|
parameters,
|
|
parameterFlags,
|
|
objectTable.getHandle(function.returnType)!);
|
|
}
|
|
|
|
void _genAllocateClosureInstance(
|
|
TreeNode node, int closureFunctionIndex, FunctionNode function) {
|
|
asm.emitPushConstant(closureFunctionIndex);
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
_genPushInstantiatorTypeArguments();
|
|
asm.emitAllocateClosure();
|
|
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitStoreLocal(temp);
|
|
|
|
if (locals.hasFunctionTypeArgsVar) {
|
|
asm.emitPush(temp);
|
|
_genPushFunctionTypeArguments();
|
|
asm.emitStoreFieldTOS(cp.addInstanceField(closureFunctionTypeArguments));
|
|
}
|
|
|
|
// Delayed type arguments are only used by generic closures.
|
|
if (function.typeParameters.isNotEmpty) {
|
|
asm.emitPush(temp);
|
|
asm.emitPushConstant(cp.addEmptyTypeArguments());
|
|
asm.emitStoreFieldTOS(cp.addInstanceField(closureDelayedTypeArguments));
|
|
}
|
|
}
|
|
|
|
void _genClosure(LocalFunction node, String name, FunctionNode function) {
|
|
final int closureFunctionIndex = _genClosureBytecode(node, name, function);
|
|
_genAllocateClosureInstance(node, closureFunctionIndex, function);
|
|
}
|
|
|
|
void _allocateContextIfNeeded() {
|
|
final int contextSize = locals.currentContextSize;
|
|
if (contextSize > 0) {
|
|
asm.emitAllocateContext(locals.currentContextId, contextSize);
|
|
|
|
if (locals.currentContextLevel > 0) {
|
|
_genDupTOS(locals.scratchVarIndexInFrame);
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
asm.emitStoreContextParent();
|
|
}
|
|
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
}
|
|
}
|
|
|
|
void _enterScope(TreeNode node) {
|
|
locals.enterScope(node);
|
|
_allocateContextIfNeeded();
|
|
if (options.emitLocalVarInfo) {
|
|
asm.localVariableTable
|
|
.enterScope(asm.offset, locals.currentContextLevel, node.fileOffset);
|
|
_startRecordingMaxPosition(node.fileOffset);
|
|
}
|
|
}
|
|
|
|
void _leaveScope() {
|
|
if (options.emitLocalVarInfo) {
|
|
asm.localVariableTable.leaveScope(asm.offset, _endRecordingMaxPosition());
|
|
}
|
|
if (locals.currentContextSize > 0) {
|
|
_genUnwindContext(locals.currentContextLevel - 1);
|
|
}
|
|
locals.leaveScope();
|
|
}
|
|
|
|
void _startRecordingMaxPosition(int fileOffset) {
|
|
savedMaxSourcePositions!.add(maxSourcePosition);
|
|
maxSourcePosition = fileOffset;
|
|
}
|
|
|
|
int _endRecordingMaxPosition() {
|
|
int localMax = maxSourcePosition;
|
|
maxSourcePosition =
|
|
math.max(localMax, savedMaxSourcePositions!.removeLast());
|
|
return localMax;
|
|
}
|
|
|
|
void _genUnwindContext(int targetContextLevel) {
|
|
int currentContextLevel = locals.currentContextLevel;
|
|
assert(currentContextLevel >= targetContextLevel);
|
|
while (currentContextLevel > targetContextLevel) {
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
asm.emitLoadContextParent();
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
--currentContextLevel;
|
|
}
|
|
}
|
|
|
|
/// Returns the list of try-finally blocks between [from] and [to],
|
|
/// ordered from inner to outer. If [to] is null, returns all enclosing
|
|
/// try-finally blocks up to the function boundary.
|
|
List<TryFinally> _getEnclosingTryFinallyBlocks(TreeNode from, TreeNode? to) {
|
|
List<TryFinally> blocks = <TryFinally>[];
|
|
TreeNode? node = from;
|
|
for (;;) {
|
|
if (node == to) {
|
|
return blocks;
|
|
}
|
|
if (node == null || node is FunctionNode || node is Member) {
|
|
if (to == null) {
|
|
return blocks;
|
|
} else {
|
|
throw 'Unable to find node $to up from $from';
|
|
}
|
|
}
|
|
// Inspect parent as we only need try-finally blocks enclosing [node]
|
|
// in the body, and not in the finally-block.
|
|
final parent = node.parent;
|
|
if (parent is TryFinally && parent.body == node) {
|
|
blocks.add(parent);
|
|
}
|
|
node = parent;
|
|
}
|
|
}
|
|
|
|
/// Appends chained [FinallyBlock]s to each try-finally in the given
|
|
/// list [tryFinallyBlocks] (ordered from inner to outer).
|
|
/// [continuation] is invoked to generate control transfer code following
|
|
/// the last finally block.
|
|
void _addFinallyBlocks(
|
|
List<TryFinally> tryFinallyBlocks, GenerateContinuation continuation) {
|
|
// Add finally blocks to all try-finally from outer to inner.
|
|
// The outermost finally block should generate continuation, each inner
|
|
// finally block should proceed to a corresponding outer block.
|
|
for (var tryFinally in tryFinallyBlocks.reversed) {
|
|
final finallyBlock = new FinallyBlock(continuation);
|
|
finallyBlocks![tryFinally]!.add(finallyBlock);
|
|
|
|
final Label nextFinally = finallyBlock.entry;
|
|
continuation = () {
|
|
asm.emitJump(nextFinally);
|
|
};
|
|
}
|
|
|
|
// Generate jump to the innermost finally (or to the original
|
|
// continuation if there are no try-finally blocks).
|
|
continuation();
|
|
}
|
|
|
|
/// Generates non-local transfer from inner node [from] into the outer
|
|
/// node, executing finally blocks on the way out. [to] can be null,
|
|
/// in such case all enclosing finally blocks are executed.
|
|
/// [continuation] is invoked to generate control transfer code following
|
|
/// the last finally block.
|
|
void _generateNonLocalControlTransfer(
|
|
TreeNode from, TreeNode to, GenerateContinuation continuation) {
|
|
if (options.emitDebuggerStops && from.fileOffset != TreeNode.noOffset) {
|
|
asm.emitDebugCheck(); // Before context is unwound.
|
|
}
|
|
List<TryFinally> tryFinallyBlocks = _getEnclosingTryFinallyBlocks(from, to);
|
|
_addFinallyBlocks(tryFinallyBlocks, continuation);
|
|
}
|
|
|
|
// For certain expressions wrapped into ExpressionStatement we can
|
|
// omit pushing result on the stack.
|
|
bool isExpressionWithoutResult(Expression expr) =>
|
|
expr.parent is ExpressionStatement &&
|
|
(expr is VariableSet ||
|
|
expr is DynamicSet ||
|
|
expr is InstanceSet ||
|
|
expr is StaticSet ||
|
|
expr is SuperPropertySet);
|
|
|
|
void _createArgumentsArray(int temp, List<DartType> typeArgs,
|
|
List<Expression> args, bool storeLastArgumentToTemp) {
|
|
final int totalCount = (typeArgs.isNotEmpty ? 1 : 0) + args.length;
|
|
|
|
_genTypeArguments([const DynamicType()]);
|
|
_genPushInt(totalCount);
|
|
asm.emitCreateArrayTOS();
|
|
|
|
asm.emitStoreLocal(temp);
|
|
|
|
int index = 0;
|
|
if (typeArgs.isNotEmpty) {
|
|
asm.emitPush(temp);
|
|
_genPushInt(index++);
|
|
_genTypeArguments(typeArgs);
|
|
asm.emitStoreIndexedTOS();
|
|
}
|
|
|
|
for (Expression arg in args) {
|
|
asm.emitPush(temp);
|
|
_genPushInt(index++);
|
|
_generateNode(arg);
|
|
if (storeLastArgumentToTemp && index == totalCount) {
|
|
// Arguments array in 'temp' is replaced with the last argument
|
|
// in order to return result of RHS value in case of setter.
|
|
asm.emitStoreLocal(temp);
|
|
}
|
|
asm.emitStoreIndexedTOS();
|
|
}
|
|
}
|
|
|
|
void _genNoSuchMethodForSuperCall(String name, int temp, int argDescCpIndex,
|
|
List<DartType> typeArgs, List<Expression> args,
|
|
{bool storeLastArgumentToTemp = false}) {
|
|
// Receiver for noSuchMethod() call.
|
|
_genPushReceiver();
|
|
|
|
// Argument 0 for _allocateInvocationMirror(): function name.
|
|
asm.emitPushConstant(cp.addName(name));
|
|
|
|
// Argument 1 for _allocateInvocationMirror(): arguments descriptor.
|
|
asm.emitPushConstant(argDescCpIndex);
|
|
|
|
// Argument 2 for _allocateInvocationMirror(): list of arguments.
|
|
_createArgumentsArray(temp, typeArgs, args, storeLastArgumentToTemp);
|
|
|
|
// Argument 3 for _allocateInvocationMirror(): isSuperInvocation flag.
|
|
asm.emitPushTrue();
|
|
|
|
_genDirectCall(
|
|
allocateInvocationMirror, objectTable.getArgDescHandle(4), 4);
|
|
|
|
final Member target = hierarchy.getDispatchTarget(
|
|
enclosingClass!.superclass!, noSuchMethodName)!;
|
|
_genDirectCall(target, objectTable.getArgDescHandle(2), 2);
|
|
}
|
|
|
|
void _genNoSuchMethodForExternal(Member node) {
|
|
if (node.isInstanceMember) {
|
|
_genPushReceiver(); // receiver.
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
asm.emitPushConstant(cp.addString(node.name.text)); // memberName.
|
|
asm.emitPushInt(0); // invocationType.
|
|
asm.emitPushInt(0); // typeArgumentsLength.
|
|
asm.emitPushNull(); // typeArguments.
|
|
asm.emitPushNull(); // arguments.
|
|
asm.emitPushNull(); // argumentNames.
|
|
_genDirectCall(
|
|
throwNewNoSuchMethodError, objectTable.getArgDescHandle(7), 7);
|
|
}
|
|
|
|
@override
|
|
void defaultTreeNode(Node node) => throw new UnsupportedOperationError(
|
|
'Unsupported node ${node.runtimeType}');
|
|
|
|
@override
|
|
void visitAsExpression(AsExpression node) {
|
|
_generateNode(node.operand);
|
|
|
|
final type = node.type;
|
|
if (_isTopType(type) || node.isUnchecked) {
|
|
return;
|
|
}
|
|
|
|
_genAssertAssignable(type,
|
|
message: node.isTypeError ? '' : symbolForTypeCast);
|
|
}
|
|
|
|
@override
|
|
void visitBoolLiteral(BoolLiteral node) {
|
|
_genPushBool(node.value);
|
|
}
|
|
|
|
@override
|
|
void visitIntLiteral(IntLiteral node) {
|
|
_genPushInt(node.value);
|
|
}
|
|
|
|
@override
|
|
void visitDoubleLiteral(DoubleLiteral node) {
|
|
final cpIndex = cp.addObjectRef(new DoubleConstant(node.value));
|
|
asm.emitPushConstant(cpIndex);
|
|
}
|
|
|
|
@override
|
|
void visitConditionalExpression(ConditionalExpression node) {
|
|
final Label otherwisePart = new Label();
|
|
final Label done = new Label();
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
|
|
_genConditionAndJumpIf(node.condition, false, otherwisePart);
|
|
|
|
_generateNode(node.then);
|
|
asm.emitPopLocal(temp);
|
|
asm.emitJump(done);
|
|
|
|
asm.bind(otherwisePart);
|
|
_generateNode(node.otherwise);
|
|
asm.emitPopLocal(temp);
|
|
|
|
asm.bind(done);
|
|
asm.emitPush(temp);
|
|
}
|
|
|
|
@override
|
|
void visitConstructorInvocation(ConstructorInvocation node) {
|
|
if (node.isConst) {
|
|
_genPushConstExpr(node);
|
|
return;
|
|
}
|
|
|
|
final constructedClass = node.constructedType.classNode;
|
|
final classIndex = cp.addClass(constructedClass);
|
|
|
|
if (hasInstantiatorTypeArguments(constructedClass)) {
|
|
_genTypeArguments(node.arguments.types,
|
|
instantiatingClass: constructedClass);
|
|
asm.emitPushConstant(cp.addClass(constructedClass));
|
|
asm.emitAllocateT();
|
|
} else {
|
|
assert(node.arguments.types.isEmpty);
|
|
asm.emitAllocate(classIndex);
|
|
}
|
|
|
|
_genDupTOS(locals.tempIndexInFrame(node));
|
|
|
|
// Remove type arguments as they are only passed to instance allocation,
|
|
// and not passed to a constructor.
|
|
final args =
|
|
new Arguments(node.arguments.positional, named: node.arguments.named)
|
|
..parent = node;
|
|
_genArguments(null, args);
|
|
_genDirectCallWithArgs(node.target, args, hasReceiver: true, node: node);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
@override
|
|
void visitFunctionExpression(FunctionExpression node) {
|
|
_genClosure(node, '<anonymous closure>', node.function);
|
|
}
|
|
|
|
@override
|
|
void visitInstantiation(Instantiation node) {
|
|
final int oldClosure = locals.tempIndexInFrame(node, tempIndex: 0);
|
|
final int newClosure = locals.tempIndexInFrame(node, tempIndex: 1);
|
|
final int typeArguments = locals.tempIndexInFrame(node, tempIndex: 2);
|
|
|
|
_generateNode(node.expression);
|
|
asm.emitStoreLocal(oldClosure);
|
|
|
|
_genTypeArguments(node.typeArguments);
|
|
asm.emitStoreLocal(typeArguments);
|
|
|
|
_genDirectCall(
|
|
boundsCheckForPartialInstantiation, objectTable.getArgDescHandle(2), 2);
|
|
asm.emitDrop1();
|
|
|
|
asm.emitPush(oldClosure);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureFunction));
|
|
asm.emitPush(oldClosure);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureContext));
|
|
asm.emitPush(oldClosure);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureInstantiatorTypeArguments));
|
|
asm.emitAllocateClosure();
|
|
asm.emitStoreLocal(newClosure);
|
|
|
|
asm.emitPush(typeArguments);
|
|
asm.emitStoreFieldTOS(cp.addInstanceField(closureDelayedTypeArguments));
|
|
|
|
asm.emitPush(newClosure);
|
|
asm.emitPush(oldClosure);
|
|
final closureFunctionTypeArgumentsCpIndex =
|
|
cp.addInstanceField(closureFunctionTypeArguments);
|
|
asm.emitLoadFieldTOS(closureFunctionTypeArgumentsCpIndex);
|
|
asm.emitStoreFieldTOS(closureFunctionTypeArgumentsCpIndex);
|
|
|
|
asm.emitPush(newClosure);
|
|
}
|
|
|
|
@override
|
|
void visitIsExpression(IsExpression node) {
|
|
_generateNode(node.operand);
|
|
_genInstanceOf(node.type);
|
|
}
|
|
|
|
@override
|
|
void visitLet(Let node) {
|
|
_enterScope(node);
|
|
_generateNode(node.variable);
|
|
_generateNode(node.body);
|
|
_leaveScope();
|
|
}
|
|
|
|
@override
|
|
void visitListLiteral(ListLiteral node) {
|
|
if (node.isConst) {
|
|
_genPushConstExpr(node);
|
|
return;
|
|
}
|
|
|
|
_genTypeArguments([node.typeArgument]);
|
|
|
|
if (node.expressions.isEmpty) {
|
|
asm.emitPushConstant(
|
|
cp.addObjectRef(new ListConstant(const DynamicType(), const [])));
|
|
} else {
|
|
_genDupTOS(locals.tempIndexInFrame(node));
|
|
_genPushInt(node.expressions.length);
|
|
asm.emitCreateArrayTOS();
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitStoreLocal(temp);
|
|
|
|
for (int i = 0; i < node.expressions.length; i++) {
|
|
asm.emitPush(temp);
|
|
_genPushInt(i);
|
|
_generateNode(node.expressions[i]);
|
|
asm.emitStoreIndexedTOS();
|
|
}
|
|
}
|
|
|
|
// _GrowableList._literal is a factory constructor.
|
|
// Type arguments passed to a factory constructor are counted as a normal
|
|
// argument and not counted in number of type arguments.
|
|
assert(growableListLiteral.isFactory);
|
|
_genDirectCall(growableListLiteral, objectTable.getArgDescHandle(2), 2);
|
|
}
|
|
|
|
@override
|
|
void visitLogicalExpression(LogicalExpression node) {
|
|
final Label shortCircuit = new Label();
|
|
final Label done = new Label();
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
final isOR = (node.operatorEnum == LogicalExpressionOperator.OR);
|
|
|
|
_genConditionAndJumpIf(node.left, isOR, shortCircuit);
|
|
|
|
bool negated = _genCondition(node.right);
|
|
if (negated) {
|
|
asm.emitBooleanNegateTOS();
|
|
}
|
|
asm.emitPopLocal(temp);
|
|
asm.emitJump(done);
|
|
|
|
asm.bind(shortCircuit);
|
|
_genPushBool(isOR);
|
|
asm.emitPopLocal(temp);
|
|
|
|
asm.bind(done);
|
|
asm.emitPush(temp);
|
|
}
|
|
|
|
@override
|
|
void visitMapLiteral(MapLiteral node) {
|
|
if (node.isConst) {
|
|
_genPushConstExpr(node);
|
|
return;
|
|
}
|
|
|
|
_genTypeArguments([node.keyType, node.valueType]);
|
|
|
|
if (node.entries.isEmpty) {
|
|
asm.emitPushConstant(
|
|
cp.addObjectRef(new ListConstant(const DynamicType(), const [])));
|
|
} else {
|
|
_genTypeArguments([const DynamicType()]);
|
|
_genPushInt(node.entries.length * 2);
|
|
asm.emitCreateArrayTOS();
|
|
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitStoreLocal(temp);
|
|
|
|
for (int i = 0; i < node.entries.length; i++) {
|
|
// key
|
|
asm.emitPush(temp);
|
|
_genPushInt(i * 2);
|
|
_generateNode(node.entries[i].key);
|
|
asm.emitStoreIndexedTOS();
|
|
// value
|
|
asm.emitPush(temp);
|
|
_genPushInt(i * 2 + 1);
|
|
_generateNode(node.entries[i].value);
|
|
asm.emitStoreIndexedTOS();
|
|
}
|
|
}
|
|
|
|
// Map._fromLiteral is a factory constructor.
|
|
// Type arguments passed to a factory constructor are counted as a normal
|
|
// argument and not counted in number of type arguments.
|
|
assert(mapFromLiteral.isFactory);
|
|
_genDirectCall(mapFromLiteral, objectTable.getArgDescHandle(2), 2);
|
|
}
|
|
|
|
void _genMethodInvocationUsingSpecializedBytecode(
|
|
Opcode opcode, InstanceInvocationExpression node) {
|
|
switch (opcode) {
|
|
case Opcode.kEqualsNull:
|
|
if (node.receiver is NullLiteral) {
|
|
_generateNode(node.arguments.positional.single);
|
|
} else {
|
|
_generateNode(node.receiver);
|
|
}
|
|
break;
|
|
|
|
case Opcode.kNegateInt:
|
|
case Opcode.kNegateDouble:
|
|
_generateNode(node.receiver);
|
|
break;
|
|
|
|
case Opcode.kAddInt:
|
|
case Opcode.kSubInt:
|
|
case Opcode.kMulInt:
|
|
case Opcode.kTruncDivInt:
|
|
case Opcode.kModInt:
|
|
case Opcode.kBitAndInt:
|
|
case Opcode.kBitOrInt:
|
|
case Opcode.kBitXorInt:
|
|
case Opcode.kShlInt:
|
|
case Opcode.kShrInt:
|
|
case Opcode.kCompareIntEq:
|
|
case Opcode.kCompareIntGt:
|
|
case Opcode.kCompareIntLt:
|
|
case Opcode.kCompareIntGe:
|
|
case Opcode.kCompareIntLe:
|
|
case Opcode.kAddDouble:
|
|
case Opcode.kSubDouble:
|
|
case Opcode.kMulDouble:
|
|
case Opcode.kDivDouble:
|
|
case Opcode.kCompareDoubleEq:
|
|
case Opcode.kCompareDoubleGt:
|
|
case Opcode.kCompareDoubleLt:
|
|
case Opcode.kCompareDoubleGe:
|
|
case Opcode.kCompareDoubleLe:
|
|
_generateNode(node.receiver);
|
|
_generateNode(node.arguments.positional.single);
|
|
break;
|
|
|
|
default:
|
|
throw 'Unexpected specialized bytecode $opcode';
|
|
}
|
|
|
|
asm.emitSpecializedBytecode(opcode);
|
|
}
|
|
|
|
bool _isUncheckedCall(
|
|
Node node, Member? interfaceTarget, Expression receiver) =>
|
|
isUncheckedCall(interfaceTarget, receiver, staticTypeContext);
|
|
|
|
void _genInstanceCall(
|
|
TreeNode node,
|
|
InvocationKind invocationKind,
|
|
Member? interfaceTarget,
|
|
Name targetName,
|
|
Expression receiver,
|
|
int totalArgCount,
|
|
ObjectHandle argDesc) {
|
|
final isDynamic = interfaceTarget == null;
|
|
final isUnchecked = invocationKind != InvocationKind.getter &&
|
|
_isUncheckedCall(node, interfaceTarget, receiver);
|
|
|
|
bool generated = false;
|
|
if (invocationKind != InvocationKind.getter && !isDynamic && !isUnchecked) {
|
|
final staticReceiverType = getStaticType(receiver, staticTypeContext);
|
|
if (isInstantiatedInterfaceCall(interfaceTarget, staticReceiverType)) {
|
|
final callCpIndex = cp.addInstantiatedInterfaceCall(
|
|
invocationKind, interfaceTarget, argDesc, staticReceiverType);
|
|
asm.emitInstantiatedInterfaceCall(callCpIndex, totalArgCount);
|
|
generated = true;
|
|
}
|
|
}
|
|
|
|
if (!generated) {
|
|
final callCpIndex = cp.addInstanceCall(
|
|
invocationKind, interfaceTarget, targetName, argDesc);
|
|
if (isDynamic) {
|
|
assert(!isUnchecked);
|
|
asm.emitDynamicCall(callCpIndex, totalArgCount);
|
|
} else if (isUnchecked) {
|
|
asm.emitUncheckedInterfaceCall(callCpIndex, totalArgCount);
|
|
} else {
|
|
asm.emitInterfaceCall(callCpIndex, totalArgCount);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitDynamicInvocation(DynamicInvocation node) {
|
|
final targetName = node.isImplicitCall ? implicitCallName : node.name;
|
|
_genMethodInvocation(node, null, targetName);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceInvocation(InstanceInvocation node) {
|
|
_genMethodInvocation(node, node.interfaceTarget, node.name);
|
|
}
|
|
|
|
@override
|
|
void visitEqualsCall(EqualsCall node) {
|
|
_generateNode(node.left);
|
|
_generateNode(node.right);
|
|
final argDesc = objectTable.getArgDescHandle(2);
|
|
_genInstanceCall(node, InvocationKind.method, coreTypes.objectEquals,
|
|
Name('=='), node.left, 2, argDesc);
|
|
}
|
|
|
|
@override
|
|
void visitEqualsNull(EqualsNull node) {
|
|
_generateNode(node.expression);
|
|
asm.emitSpecializedBytecode(Opcode.kEqualsNull);
|
|
}
|
|
|
|
void _genMethodInvocation(InstanceInvocationExpression node,
|
|
Procedure? interfaceTarget, Name targetName) {
|
|
final Opcode? opcode = recognizedMethods.specializedBytecodeFor(node);
|
|
if (opcode != null) {
|
|
_genMethodInvocationUsingSpecializedBytecode(opcode, node);
|
|
return;
|
|
}
|
|
final args = node.arguments;
|
|
final totalArgCount = args.positional.length +
|
|
args.named.length +
|
|
1 /* receiver */ +
|
|
(args.types.isNotEmpty ? 1 : 0) /* type arguments */;
|
|
if (totalArgCount >= argumentsLimit) {
|
|
throw 'Too many arguments';
|
|
}
|
|
|
|
_genArguments(node.receiver, args);
|
|
|
|
final argDesc =
|
|
objectTable.getArgDescHandleByArguments(args, hasReceiver: true);
|
|
|
|
_genInstanceCall(node, InvocationKind.method, interfaceTarget, targetName,
|
|
node.receiver, totalArgCount, argDesc);
|
|
}
|
|
|
|
@override
|
|
void visitFunctionInvocation(FunctionInvocation node) {
|
|
final args = node.arguments;
|
|
final totalArgCount = args.positional.length +
|
|
args.named.length +
|
|
1 /* receiver */ +
|
|
(args.types.isNotEmpty ? 1 : 0) /* type arguments */;
|
|
if (totalArgCount >= argumentsLimit) {
|
|
throw 'Too many arguments';
|
|
}
|
|
// Front-end guarantees that all calls with known function type
|
|
// do not need any argument type checks.
|
|
if (node.kind == FunctionAccessKind.FunctionType) {
|
|
final int receiverTemp = locals.tempIndexInFrame(node);
|
|
_genArguments(node.receiver, args, storeReceiverToLocal: receiverTemp);
|
|
// Duplicate receiver (closure) for UncheckedClosureCall.
|
|
asm.emitPush(receiverTemp);
|
|
final argDescCpIndex = cp.addArgDescByArguments(args, hasReceiver: true);
|
|
asm.emitUncheckedClosureCall(argDescCpIndex, totalArgCount);
|
|
return;
|
|
}
|
|
|
|
_genArguments(node.receiver, args);
|
|
final argDesc =
|
|
objectTable.getArgDescHandleByArguments(args, hasReceiver: true);
|
|
_genInstanceCall(node, InvocationKind.method, null, Name.callName,
|
|
node.receiver, totalArgCount, argDesc);
|
|
}
|
|
|
|
@override
|
|
void visitLocalFunctionInvocation(LocalFunctionInvocation node) {
|
|
final args = node.arguments;
|
|
final totalArgCount = args.positional.length +
|
|
args.named.length +
|
|
1 /* receiver */ +
|
|
(args.types.isNotEmpty ? 1 : 0) /* type arguments */;
|
|
if (totalArgCount >= argumentsLimit) {
|
|
throw 'Too many arguments';
|
|
}
|
|
|
|
if (args.types.isNotEmpty) {
|
|
_genTypeArguments(args.types);
|
|
}
|
|
_genLoadVar(node.variable);
|
|
_generateNodeList(args.positional);
|
|
args.named.forEach((NamedExpression ne) => _generateNode(ne.value));
|
|
|
|
// Duplicate receiver (closure) for UncheckedClosureCall.
|
|
_genLoadVar(node.variable);
|
|
final argDescCpIndex = cp.addArgDescByArguments(args, hasReceiver: true);
|
|
asm.emitUncheckedClosureCall(argDescCpIndex, totalArgCount);
|
|
}
|
|
|
|
@override
|
|
void visitDynamicGet(DynamicGet node) {
|
|
_genPropertyGet(node, node.name, null, node.receiver);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceGet(InstanceGet node) {
|
|
_genPropertyGet(node, node.name, node.interfaceTarget, node.receiver);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceTearOff(InstanceTearOff node) {
|
|
_genPropertyGet(node, node.name, node.interfaceTarget, node.receiver);
|
|
}
|
|
|
|
void _genPropertyGet(Expression node, Name name, Member? interfaceTarget,
|
|
Expression receiver) {
|
|
_generateNode(receiver);
|
|
final argDesc = objectTable.getArgDescHandle(1);
|
|
|
|
_genInstanceCall(node, InvocationKind.getter, interfaceTarget, name,
|
|
receiver, 1, argDesc);
|
|
}
|
|
|
|
@override
|
|
void visitDynamicSet(DynamicSet node) {
|
|
_genPropertySet(node, node.name, null, node.receiver, node.value);
|
|
}
|
|
|
|
@override
|
|
void visitInstanceSet(InstanceSet node) {
|
|
_genPropertySet(
|
|
node, node.name, node.interfaceTarget, node.receiver, node.value);
|
|
}
|
|
|
|
void _genPropertySet(Expression node, Name name, Member? interfaceTarget,
|
|
Expression receiver, Expression value) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
final bool hasResult = !isExpressionWithoutResult(node);
|
|
|
|
_generateNode(receiver);
|
|
|
|
_generateNode(value);
|
|
|
|
if (hasResult) {
|
|
asm.emitStoreLocal(temp);
|
|
}
|
|
|
|
const int numArguments = 2;
|
|
final argDesc = objectTable.getArgDescHandle(numArguments);
|
|
|
|
_genInstanceCall(node, InvocationKind.setter, interfaceTarget, name,
|
|
receiver, numArguments, argDesc);
|
|
|
|
asm.emitDrop1();
|
|
|
|
if (hasResult) {
|
|
asm.emitPush(temp);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitSuperMethodInvocation(SuperMethodInvocation node) {
|
|
final args = node.arguments;
|
|
final Member? target =
|
|
hierarchy.getDispatchTarget(enclosingClass!.superclass!, node.name);
|
|
if (target == null) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
_genNoSuchMethodForSuperCall(
|
|
node.name.text,
|
|
temp,
|
|
cp.addArgDescByArguments(args, hasReceiver: true),
|
|
args.types,
|
|
<Expression>[new ThisExpression()]
|
|
..addAll(args.positional)
|
|
..addAll(args.named.map((x) => x.value)));
|
|
return;
|
|
}
|
|
if (!(target is Procedure && !target.isGetter)) {
|
|
throw new UnsupportedOperationError(
|
|
'Unsupported SuperMethodInvocation with target ${target.runtimeType} $target');
|
|
}
|
|
_genArguments(new ThisExpression(), args);
|
|
_genDirectCallWithArgs(target, args,
|
|
hasReceiver: true, isUnchecked: true, node: node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperPropertyGet(SuperPropertyGet node) {
|
|
final Member? target =
|
|
hierarchy.getDispatchTarget(enclosingClass!.superclass!, node.name);
|
|
if (target == null) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
_genNoSuchMethodForSuperCall(node.name.text, temp, cp.addArgDesc(1), [],
|
|
<Expression>[new ThisExpression()]);
|
|
return;
|
|
}
|
|
_genPushReceiver();
|
|
_genDirectCall(target, objectTable.getArgDescHandle(1), 1,
|
|
isGet: true, node: node);
|
|
}
|
|
|
|
@override
|
|
void visitSuperPropertySet(SuperPropertySet node) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
final bool hasResult = !isExpressionWithoutResult(node);
|
|
|
|
final Member? target = hierarchy.getDispatchTarget(
|
|
enclosingClass!.superclass!, node.name,
|
|
setter: true);
|
|
if (target == null) {
|
|
_genNoSuchMethodForSuperCall(node.name.text, temp, cp.addArgDesc(2), [],
|
|
<Expression>[new ThisExpression(), node.value],
|
|
storeLastArgumentToTemp: hasResult);
|
|
} else {
|
|
_genPushReceiver();
|
|
_generateNode(node.value);
|
|
|
|
if (hasResult) {
|
|
asm.emitStoreLocal(temp);
|
|
}
|
|
|
|
assert(target is Field || (target is Procedure && target.isSetter));
|
|
_genDirectCall(target, objectTable.getArgDescHandle(2), 2,
|
|
isSet: true, isUnchecked: true, node: node);
|
|
}
|
|
|
|
asm.emitDrop1();
|
|
|
|
if (hasResult) {
|
|
asm.emitPush(temp);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitNot(Not node) {
|
|
bool negated = _genCondition(node.operand);
|
|
if (!negated) {
|
|
asm.emitBooleanNegateTOS();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitNullCheck(NullCheck node) {
|
|
_generateNode(node.operand);
|
|
final operandTemp = locals.tempIndexInFrame(node);
|
|
asm.emitStoreLocal(operandTemp);
|
|
asm.emitPush(operandTemp);
|
|
asm.emitNullCheck(cp.addObjectRef(null));
|
|
}
|
|
|
|
@override
|
|
void visitNullLiteral(NullLiteral node) {
|
|
asm.emitPushNull();
|
|
}
|
|
|
|
@override
|
|
void visitRethrow(Rethrow node) {
|
|
TryCatch tryCatch;
|
|
for (var parent = node.parent;; parent = parent.parent) {
|
|
if (parent is Catch) {
|
|
tryCatch = parent.parent as TryCatch;
|
|
break;
|
|
}
|
|
if (parent == null || parent is FunctionNode) {
|
|
throw 'Unable to find enclosing catch for $node';
|
|
}
|
|
}
|
|
tryCatches![tryCatch]!.needsStackTrace = true;
|
|
|
|
if (options.emitDebuggerStops) {
|
|
asm.emitDebugCheck(); // Allow breakpoint on explicit rethrow statement.
|
|
}
|
|
_genRethrow(tryCatch);
|
|
}
|
|
|
|
bool _hasNonTrivialInitializer(Field field) {
|
|
final initializer = field.initializer;
|
|
if (initializer == null) return false;
|
|
if (options.emitInstanceFieldInitializers && !field.isStatic) {
|
|
// Hot reload needs initializers for all instance fields
|
|
// except fields initialized with null.
|
|
return !_isNullInitializer(initializer);
|
|
}
|
|
return !_isTrivialInitializer(initializer);
|
|
}
|
|
|
|
bool _isTrivialInitializer(Expression? initializer) {
|
|
if (initializer == null) return false;
|
|
if (initializer is StringLiteral ||
|
|
initializer is BoolLiteral ||
|
|
initializer is IntLiteral ||
|
|
initializer is DoubleLiteral ||
|
|
initializer is NullLiteral) {
|
|
return true;
|
|
}
|
|
if (initializer is ConstantExpression &&
|
|
initializer.constant is PrimitiveConstant) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool _isNullInitializer(Expression? initializer) =>
|
|
initializer is NullLiteral ||
|
|
(initializer is ConstantExpression &&
|
|
initializer.constant is NullConstant);
|
|
|
|
@override
|
|
void visitStaticGet(StaticGet node) {
|
|
final target = node.target;
|
|
if (target is Field) {
|
|
if (target.isConst) {
|
|
_genPushConstExpr(target.initializer!);
|
|
} else if (!_needsGetter(target)) {
|
|
asm.emitLoadStatic(cp.addStaticField(target));
|
|
} else {
|
|
_genDirectCall(target, objectTable.getArgDescHandle(0), 0,
|
|
isGet: true, node: node);
|
|
}
|
|
} else if (target is Procedure) {
|
|
if (target.isGetter) {
|
|
_genDirectCall(target, objectTable.getArgDescHandle(0), 0,
|
|
isGet: true, node: node);
|
|
} else {
|
|
throw 'Unexpected target for StaticGet: ${target.runtimeType} $target';
|
|
}
|
|
} else {
|
|
throw 'Unexpected target for StaticGet: ${target.runtimeType} $target';
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitStaticInvocation(StaticInvocation node) {
|
|
if (node.isConst) {
|
|
_genPushConstExpr(node);
|
|
return;
|
|
}
|
|
Arguments args = node.arguments;
|
|
final target = node.target;
|
|
// Handle built-in methods with special semantics.
|
|
if (target == unsafeCast || target == reachabilityFence) {
|
|
// Just evaluate argument.
|
|
assert(args.named.isEmpty);
|
|
_generateNode(args.positional.single);
|
|
return;
|
|
} else if (target == nativeEffect) {
|
|
// Skip over AST of the argument, return null.
|
|
asm.emitPushNull();
|
|
return;
|
|
}
|
|
if (target.isFactory) {
|
|
final constructedClass = target.enclosingClass!;
|
|
if (hasInstantiatorTypeArguments(constructedClass)) {
|
|
_genTypeArguments(args.types, instantiatingClass: constructedClass);
|
|
} else {
|
|
assert(args.types.isEmpty);
|
|
// VM needs type arguments for every invocation of a factory
|
|
// constructor. TODO(alexmarkov): Clean this up.
|
|
asm.emitPushNull();
|
|
}
|
|
args =
|
|
new Arguments(node.arguments.positional, named: node.arguments.named)
|
|
..parent = node;
|
|
}
|
|
_genArguments(null, args);
|
|
_genDirectCallWithArgs(target, args,
|
|
isFactory: target.isFactory, node: node);
|
|
}
|
|
|
|
@override
|
|
void visitStaticSet(StaticSet node) {
|
|
final bool hasResult = !isExpressionWithoutResult(node);
|
|
|
|
_generateNode(node.value);
|
|
|
|
if (hasResult) {
|
|
_genDupTOS(locals.tempIndexInFrame(node));
|
|
}
|
|
|
|
final target = node.target;
|
|
if (target is Field && !_needsSetter(target)) {
|
|
if (options.emitDebuggerStops &&
|
|
_variableSetNeedsDebugCheck(node.value)) {
|
|
asm.emitDebugCheck();
|
|
}
|
|
int cpIndex = cp.addStaticField(target);
|
|
asm.emitStoreStaticTOS(cpIndex);
|
|
} else {
|
|
_genDirectCall(target, objectTable.getArgDescHandle(1), 1,
|
|
isSet: true, node: node);
|
|
asm.emitDrop1();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitStringConcatenation(StringConcatenation node) {
|
|
if (node.expressions.length == 1) {
|
|
_generateNode(node.expressions.single);
|
|
_genDirectCall(interpolateSingle, objectTable.getArgDescHandle(1), 1);
|
|
} else {
|
|
asm.emitPushNull();
|
|
_genPushInt(node.expressions.length);
|
|
asm.emitCreateArrayTOS();
|
|
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitStoreLocal(temp);
|
|
|
|
for (int i = 0; i < node.expressions.length; i++) {
|
|
asm.emitPush(temp);
|
|
_genPushInt(i);
|
|
_generateNode(node.expressions[i]);
|
|
asm.emitStoreIndexedTOS();
|
|
}
|
|
|
|
_genDirectCall(interpolate, objectTable.getArgDescHandle(1), 1);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitStringLiteral(StringLiteral node) {
|
|
final cpIndex = cp.addString(node.value);
|
|
asm.emitPushConstant(cpIndex);
|
|
}
|
|
|
|
@override
|
|
void visitSymbolLiteral(SymbolLiteral node) {
|
|
_genPushConstExpr(node);
|
|
}
|
|
|
|
@override
|
|
void visitThisExpression(ThisExpression node) {
|
|
_genPushReceiver();
|
|
}
|
|
|
|
@override
|
|
void visitThrow(Throw node) {
|
|
_generateNode(node.expression);
|
|
|
|
if (options.emitDebuggerStops) {
|
|
asm.emitDebugCheck();
|
|
}
|
|
asm.emitThrow(0);
|
|
}
|
|
|
|
@override
|
|
void visitTypeLiteral(TypeLiteral node) {
|
|
final DartType type = node.type;
|
|
if (!hasFreeTypeParameters([type])) {
|
|
// Instantiated type should not depend on
|
|
// the type parameters of the enclosing function.
|
|
objectTable.withoutEnclosingFunctionTypeParameters(() {
|
|
asm.emitPushConstant(cp.addType(type));
|
|
});
|
|
} else {
|
|
_genPushInstantiatorAndFunctionTypeArguments([type]);
|
|
asm.emitInstantiateType(cp.addType(type));
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitVariableGet(VariableGet node) {
|
|
final v = node.variable;
|
|
if (v.isConst) {
|
|
_genPushConstExpr(v.initializer!);
|
|
} else if (v.isLate) {
|
|
_genLoadVar(v);
|
|
|
|
final Label done = new Label();
|
|
asm.emitJumpIfInitialized(done);
|
|
|
|
final init = v.initializer;
|
|
if (init != null) {
|
|
_genPushContextIfCaptured(v);
|
|
// Late local variable initializers are transformed to wrap the
|
|
// initializer in a closure (see late_var_init_transformer.dart). The
|
|
// closure call needs one temporary, so withTemp lets us use this
|
|
// VariableGet's temporary when visiting the initializer.
|
|
assert(init is LocalFunctionInvocation &&
|
|
init.arguments.positional.isEmpty);
|
|
locals.withTemp(
|
|
init, locals.tempIndexInFrame(node), () => _generateNode(init));
|
|
if (v.isFinal) {
|
|
// Check that variable was not assigned during initialization.
|
|
_genLoadVar(v);
|
|
|
|
final error = Label();
|
|
final store = Label();
|
|
asm.emitJumpIfInitialized(error);
|
|
asm.emitJump(store);
|
|
|
|
asm.bind(error);
|
|
asm.emitPushConstant(cp.addName(v.name!));
|
|
_genDirectCall(throwLocalAssignedDuringInitialization,
|
|
objectTable.getArgDescHandle(1), 1);
|
|
asm.emitDrop1();
|
|
|
|
asm.bind(store);
|
|
}
|
|
_genStoreVar(v);
|
|
} else {
|
|
asm.emitPushConstant(cp.addName(v.name!));
|
|
_genDirectCall(
|
|
throwLocalNotInitialized, objectTable.getArgDescHandle(1), 1);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
asm.bind(done);
|
|
_genLoadVar(v);
|
|
} else {
|
|
_genLoadVar(v);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitVariableSet(VariableSet node) {
|
|
final v = node.variable;
|
|
final bool hasResult = !isExpressionWithoutResult(node);
|
|
final bool isLateFinal = v.isLate && v.isFinal;
|
|
|
|
if (!isLateFinal) {
|
|
_genPushContextIfCaptured(v);
|
|
}
|
|
|
|
_generateNode(node.value);
|
|
|
|
if (options.emitDebuggerStops && _variableSetNeedsDebugCheck(node.value)) {
|
|
asm.emitDebugCheck();
|
|
}
|
|
|
|
if (isLateFinal) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitPopLocal(temp);
|
|
|
|
final Label error = new Label();
|
|
final Label done = new Label();
|
|
_genLoadVar(v);
|
|
asm.emitJumpIfInitialized(error);
|
|
|
|
_genPushContextIfCaptured(v);
|
|
asm.emitPush(temp);
|
|
_genStoreVar(v);
|
|
asm.emitJump(done);
|
|
|
|
asm.bind(error);
|
|
asm.emitPushConstant(cp.addName(v.name!));
|
|
_genDirectCall(
|
|
throwLocalAlreadyInitialized, objectTable.getArgDescHandle(1), 1);
|
|
asm.emitDrop1();
|
|
|
|
asm.bind(done);
|
|
|
|
if (hasResult) {
|
|
asm.emitPush(temp);
|
|
}
|
|
} else if (locals.isCaptured(v)) {
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
if (hasResult) {
|
|
asm.emitStoreLocal(temp);
|
|
}
|
|
|
|
_genStoreVar(v);
|
|
|
|
if (hasResult) {
|
|
asm.emitPush(temp);
|
|
}
|
|
} else {
|
|
final int localIndex = locals.getVarIndexInFrame(v);
|
|
if (hasResult) {
|
|
asm.emitStoreLocal(localIndex);
|
|
} else {
|
|
asm.emitPopLocal(localIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _variableSetNeedsDebugCheck(Expression rhs) =>
|
|
rhs is BasicLiteral ||
|
|
rhs is ConstantExpression ||
|
|
rhs is StaticGet ||
|
|
rhs is FunctionExpression ||
|
|
rhs is VariableGet ||
|
|
rhs is AsExpression;
|
|
|
|
void _genFutureNull() {
|
|
asm.emitPushNull();
|
|
_genDirectCall(futureValue, objectTable.getArgDescHandle(1), 1);
|
|
}
|
|
|
|
@override
|
|
void visitLoadLibrary(LoadLibrary node) {
|
|
_genFutureNull();
|
|
}
|
|
|
|
@override
|
|
void visitCheckLibraryIsLoaded(CheckLibraryIsLoaded node) {
|
|
_genFutureNull();
|
|
}
|
|
|
|
@override
|
|
void visitAssertStatement(AssertStatement node) {
|
|
if (!options.enableAsserts) {
|
|
return;
|
|
}
|
|
|
|
final Label done = new Label();
|
|
asm.emitJumpIfNoAsserts(done);
|
|
|
|
_genConditionAndJumpIf(node.condition, true, done);
|
|
|
|
final fileUri = node.location!.file;
|
|
final source = node.enclosingComponent!.uriToSource[fileUri]!;
|
|
final conditionSource = source.text
|
|
.substring(node.conditionStartOffset, node.conditionEndOffset);
|
|
final location = source.getLocation(fileUri, node.conditionStartOffset);
|
|
asm.emitPushConstant(cp.addString(conditionSource));
|
|
asm.emitPushConstant(cp.addString(fileUri.toString()));
|
|
_genPushInt(options.omitAssertSourcePositions ? 0 : location.line);
|
|
_genPushInt(options.omitAssertSourcePositions ? 0 : location.column);
|
|
|
|
if (node.message != null) {
|
|
_generateNode(node.message);
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
|
|
_genDirectCall(
|
|
throwNewSourceAssertionError, objectTable.getArgDescHandle(5), 5);
|
|
asm.emitDrop1();
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
@override
|
|
void visitBlock(Block node) {
|
|
_enterScope(node);
|
|
_generateNodeList(node.statements);
|
|
_leaveScope();
|
|
}
|
|
|
|
@override
|
|
void visitAssertBlock(AssertBlock node) {
|
|
if (!options.enableAsserts) {
|
|
return;
|
|
}
|
|
|
|
final Label done = new Label();
|
|
asm.emitJumpIfNoAsserts(done);
|
|
|
|
_enterScope(node);
|
|
_generateNodeList(node.statements);
|
|
_leaveScope();
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
@override
|
|
void visitBlockExpression(BlockExpression node) {
|
|
_enterScope(node);
|
|
_generateNodeList(node.body.statements);
|
|
_generateNode(node.value);
|
|
_leaveScope();
|
|
}
|
|
|
|
@override
|
|
void visitBreakStatement(BreakStatement node) {
|
|
final targetLabel = labeledStatements?[node.target] ??
|
|
(throw 'Target label ${node.target} was not registered for break $node');
|
|
final targetContextLevel = contextLevels![node.target]!;
|
|
|
|
_generateNonLocalControlTransfer(node, node.target, () {
|
|
_genUnwindContext(targetContextLevel);
|
|
asm.emitJump(targetLabel);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void visitContinueSwitchStatement(ContinueSwitchStatement node) {
|
|
final targetLabel = switchCases?[node.target] ??
|
|
(throw 'Target label ${node.target} was not registered for continue-switch $node');
|
|
final targetContextLevel = contextLevels![node.target.parent]!;
|
|
|
|
_generateNonLocalControlTransfer(node, node.target.parent!, () {
|
|
_genUnwindContext(targetContextLevel);
|
|
asm.emitJump(targetLabel);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void visitDoStatement(DoStatement node) {
|
|
if (asm.isUnreachable) {
|
|
// Bail out before binding a label which allows backward jumps,
|
|
// as it is not handled by local unreachable code elimination.
|
|
return;
|
|
}
|
|
|
|
final Label join = new Label(allowsBackwardJumps: true);
|
|
asm.bind(join);
|
|
|
|
asm.emitCheckStack(++currentLoopDepth);
|
|
|
|
_generateNode(node.body);
|
|
|
|
_genConditionAndJumpIf(node.condition, true, join);
|
|
|
|
--currentLoopDepth;
|
|
}
|
|
|
|
@override
|
|
void visitEmptyStatement(EmptyStatement node) {
|
|
// no-op
|
|
}
|
|
|
|
@override
|
|
void visitExpressionStatement(ExpressionStatement node) {
|
|
final expr = node.expression;
|
|
_generateNode(expr);
|
|
if (!isExpressionWithoutResult(expr)) {
|
|
asm.emitDrop1();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitForInStatement(ForInStatement node) {
|
|
// Should be lowered by the async transformation.
|
|
throw "unreachable";
|
|
}
|
|
|
|
@override
|
|
void visitForStatement(ForStatement node) {
|
|
_enterScope(node);
|
|
try {
|
|
_generateNodeList(node.variables);
|
|
|
|
if (asm.isUnreachable) {
|
|
// Bail out before binding a label which allows backward jumps,
|
|
// as it is not handled by local unreachable code elimination.
|
|
return;
|
|
}
|
|
|
|
final Label done = new Label();
|
|
final Label join = new Label(allowsBackwardJumps: true);
|
|
asm.bind(join);
|
|
|
|
asm.emitCheckStack(++currentLoopDepth);
|
|
|
|
final condition = node.condition;
|
|
if (condition != null) {
|
|
_genConditionAndJumpIf(condition, false, done);
|
|
}
|
|
|
|
_generateNode(node.body);
|
|
|
|
if (locals.currentContextSize > 0) {
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
asm.emitCloneContext(
|
|
locals.currentContextId, locals.currentContextSize);
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
}
|
|
|
|
for (var update in node.updates) {
|
|
_generateNode(update);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
asm.emitJump(join);
|
|
|
|
asm.bind(done);
|
|
--currentLoopDepth;
|
|
} finally {
|
|
_leaveScope();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitFunctionDeclaration(ast.FunctionDeclaration node) {
|
|
if (options.emitDebuggerStops) {
|
|
asm.emitDebugCheck();
|
|
}
|
|
_genPushContextIfCaptured(node.variable);
|
|
_genClosure(node, node.variable.name!, node.function);
|
|
_genStoreVar(node.variable);
|
|
}
|
|
|
|
@override
|
|
void visitIfStatement(IfStatement node) {
|
|
final Label otherwisePart = new Label();
|
|
|
|
_genConditionAndJumpIf(node.condition, false, otherwisePart);
|
|
|
|
_generateNode(node.then);
|
|
|
|
if (node.otherwise != null) {
|
|
final Label done = new Label();
|
|
asm.emitJump(done);
|
|
asm.bind(otherwisePart);
|
|
_generateNode(node.otherwise);
|
|
asm.bind(done);
|
|
} else {
|
|
asm.bind(otherwisePart);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitLabeledStatement(LabeledStatement node) {
|
|
final label = new Label();
|
|
final labeledStatements =
|
|
this.labeledStatements ??= <LabeledStatement, Label>{};
|
|
labeledStatements[node] = label;
|
|
final contextLevels = this.contextLevels ??= <TreeNode, int>{};
|
|
contextLevels[node] = locals.currentContextLevel;
|
|
_generateNode(node.body);
|
|
asm.bind(label);
|
|
labeledStatements.remove(node);
|
|
contextLevels.remove(node);
|
|
}
|
|
|
|
@override
|
|
void visitReturnStatement(ReturnStatement node) {
|
|
final expr = node.expression ?? new NullLiteral();
|
|
|
|
final List<TryFinally> tryFinallyBlocks =
|
|
_getEnclosingTryFinallyBlocks(node, null);
|
|
if (tryFinallyBlocks.isEmpty) {
|
|
_generateNode(expr);
|
|
_genReturnTOS();
|
|
} else {
|
|
if (options.emitDebuggerStops) {
|
|
// Stop on the return statement before executing finally blocks.
|
|
asm.emitDebugCheck();
|
|
}
|
|
if (expr is BasicLiteral) {
|
|
_addFinallyBlocks(tryFinallyBlocks, () {
|
|
_generateNode(expr);
|
|
_genReturnTOS();
|
|
});
|
|
} else {
|
|
// Keep return value in a variable as try-catch statements
|
|
// inside finally can zap expression stack.
|
|
_generateNode(node.expression);
|
|
asm.emitPopLocal(locals.returnVarIndexInFrame);
|
|
|
|
_addFinallyBlocks(tryFinallyBlocks, () {
|
|
asm.emitPush(locals.returnVarIndexInFrame);
|
|
_genReturnTOS();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitSwitchStatement(SwitchStatement node) {
|
|
final contextLevels = this.contextLevels ??= <TreeNode, int>{};
|
|
contextLevels[node] = locals.currentContextLevel;
|
|
|
|
_generateNode(node.expression);
|
|
|
|
if (asm.isUnreachable) {
|
|
// Bail out before binding labels which allow backward jumps,
|
|
// as they are not handled by local unreachable code elimination.
|
|
return;
|
|
}
|
|
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitPopLocal(temp);
|
|
|
|
final Label done = new Label();
|
|
final List<Label> caseLabels = new List<Label>.generate(
|
|
node.cases.length, (_) => new Label(allowsBackwardJumps: true));
|
|
final equalsArgDesc = objectTable.getArgDescHandle(2);
|
|
|
|
final switchCases = this.switchCases ??= <SwitchCase, Label>{};
|
|
|
|
Label defaultLabel = done;
|
|
for (int i = 0; i < node.cases.length; i++) {
|
|
final SwitchCase switchCase = node.cases[i];
|
|
final Label caseLabel = caseLabels[i];
|
|
switchCases[switchCase] = caseLabel;
|
|
|
|
if (switchCase.isDefault) {
|
|
defaultLabel = caseLabel;
|
|
} else {
|
|
final savedSourcePosition = asm.currentSourcePosition;
|
|
for (int i = 0; i < switchCase.expressions.length; ++i) {
|
|
_recordSourcePosition(switchCase.expressionOffsets[i]);
|
|
_genPushConstExpr(switchCase.expressions[i]);
|
|
asm.emitPush(temp);
|
|
asm.emitInterfaceCall(
|
|
cp.addInterfaceCall(
|
|
InvocationKind.method, coreTypes.objectEquals, equalsArgDesc),
|
|
2);
|
|
asm.emitJumpIfTrue(caseLabel);
|
|
}
|
|
asm.currentSourcePosition = savedSourcePosition;
|
|
}
|
|
}
|
|
|
|
asm.emitJump(defaultLabel);
|
|
|
|
for (int i = 0; i < node.cases.length; i++) {
|
|
final SwitchCase switchCase = node.cases[i];
|
|
final Label caseLabel = caseLabels[i];
|
|
|
|
asm.bind(caseLabel);
|
|
_generateNode(switchCase.body);
|
|
|
|
// Front-end issues a compile-time error if there is a fallthrough
|
|
// between cases. Also, default case should be the last one.
|
|
}
|
|
|
|
asm.bind(done);
|
|
node.cases.forEach(switchCases.remove);
|
|
contextLevels.remove(node);
|
|
}
|
|
|
|
bool _isTryBlock(TreeNode node) => node is TryCatch || node is TryFinally;
|
|
|
|
int _savedContextVar(TreeNode node) {
|
|
assert(_isTryBlock(node));
|
|
assert(locals.capturedSavedContextVar(node) == null);
|
|
return locals.tempIndexInFrame(node, tempIndex: 0);
|
|
}
|
|
|
|
// Exception var occupies the same slot as saved context, so context
|
|
// should be restored first, before loading exception.
|
|
int _exceptionVar(TreeNode node) {
|
|
assert(_isTryBlock(node));
|
|
return locals.tempIndexInFrame(node, tempIndex: 0);
|
|
}
|
|
|
|
int _stackTraceVar(TreeNode node) {
|
|
assert(_isTryBlock(node));
|
|
return locals.tempIndexInFrame(node, tempIndex: 1);
|
|
}
|
|
|
|
void _saveContextForTryBlock(TreeNode node) {
|
|
if (!locals.hasContextVar) {
|
|
return;
|
|
}
|
|
asm.emitPush(locals.contextVarIndexInFrame);
|
|
asm.emitPopLocal(_savedContextVar(node));
|
|
}
|
|
|
|
void _restoreContextForTryBlock(TreeNode node) {
|
|
if (!locals.hasContextVar) {
|
|
return;
|
|
}
|
|
final capturedSavedContextVar = locals.capturedSavedContextVar(node);
|
|
if (capturedSavedContextVar != null) {
|
|
// 1. Restore context from closure var.
|
|
// This context has a context level at frame entry.
|
|
asm.emitPush(locals.closureVarIndexInFrame);
|
|
asm.emitLoadFieldTOS(cp.addInstanceField(closureContext));
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
|
|
// 2. Restore context from captured :saved_try_context_var${depth}.
|
|
assert(locals.isCaptured(capturedSavedContextVar));
|
|
_genLoadVar(capturedSavedContextVar,
|
|
currentContextLevel: locals.contextLevelAtEntry);
|
|
} else {
|
|
asm.emitPush(_savedContextVar(node));
|
|
}
|
|
asm.emitPopLocal(locals.contextVarIndexInFrame);
|
|
}
|
|
|
|
/// Start try block
|
|
TryBlock _startTryBlock(TreeNode node) {
|
|
assert(_isTryBlock(node));
|
|
|
|
_saveContextForTryBlock(node);
|
|
|
|
return asm.exceptionsTable.enterTryBlock(asm.offset);
|
|
}
|
|
|
|
/// End try block and start its handler.
|
|
void _endTryBlock(TreeNode node, TryBlock tryBlock) {
|
|
tryBlock.endPC = asm.offset;
|
|
tryBlock.handlerPC = asm.offset;
|
|
|
|
// Exception handlers are reachable although there are no labels or jumps.
|
|
asm.isUnreachable = false;
|
|
|
|
asm.emitSetFrame(locals.frameSize);
|
|
|
|
_restoreContextForTryBlock(node);
|
|
|
|
asm.emitMoveSpecial(SpecialIndex.exception, _exceptionVar(node));
|
|
asm.emitMoveSpecial(SpecialIndex.stackTrace, _stackTraceVar(node));
|
|
|
|
final capturedExceptionVar = locals.capturedExceptionVar(node);
|
|
if (capturedExceptionVar != null) {
|
|
_genPushContextForVariable(capturedExceptionVar);
|
|
asm.emitPush(_exceptionVar(node));
|
|
_genStoreVar(capturedExceptionVar);
|
|
}
|
|
|
|
final capturedStackTraceVar = locals.capturedStackTraceVar(node);
|
|
if (capturedStackTraceVar != null) {
|
|
_genPushContextForVariable(capturedStackTraceVar);
|
|
asm.emitPush(_stackTraceVar(node));
|
|
_genStoreVar(capturedStackTraceVar);
|
|
}
|
|
}
|
|
|
|
void _genRethrow(TreeNode node) {
|
|
final capturedExceptionVar = locals.capturedExceptionVar(node);
|
|
if (capturedExceptionVar != null) {
|
|
assert(locals.isCaptured(capturedExceptionVar));
|
|
_genLoadVar(capturedExceptionVar);
|
|
} else {
|
|
asm.emitPush(_exceptionVar(node));
|
|
}
|
|
|
|
final capturedStackTraceVar = locals.capturedStackTraceVar(node);
|
|
if (capturedStackTraceVar != null) {
|
|
assert(locals.isCaptured(capturedStackTraceVar));
|
|
_genLoadVar(capturedStackTraceVar);
|
|
} else {
|
|
asm.emitPush(_stackTraceVar(node));
|
|
}
|
|
|
|
asm.emitThrow(1);
|
|
}
|
|
|
|
@override
|
|
void visitTryCatch(TryCatch node) {
|
|
if (asm.isUnreachable) {
|
|
return;
|
|
}
|
|
|
|
final Label done = new Label();
|
|
|
|
final TryBlock tryBlock = _startTryBlock(node);
|
|
tryBlock.isSynthetic = node.isSynthetic;
|
|
final tryCatches = this.tryCatches ??= <TryCatch, TryBlock>{};
|
|
tryCatches[node] = tryBlock; // Used by rethrow.
|
|
|
|
_generateNode(node.body);
|
|
asm.emitJump(done);
|
|
|
|
_endTryBlock(node, tryBlock);
|
|
|
|
final int exception = _exceptionVar(node);
|
|
final int stackTrace = _stackTraceVar(node);
|
|
|
|
bool hasCatchAll = false;
|
|
|
|
final savedSourcePosition = asm.currentSourcePosition;
|
|
for (Catch catchClause in node.catches) {
|
|
_recordSourcePosition(catchClause.fileOffset);
|
|
tryBlock.types.add(cp.addType(catchClause.guard));
|
|
|
|
Label? skipCatch;
|
|
final guardType = catchClause.guard;
|
|
// Exception objects are guaranteed to be non-nullable, so
|
|
// non-nullable Object is also a catch-all type.
|
|
if (guardType is DynamicType ||
|
|
(guardType is InterfaceType &&
|
|
guardType.classNode == coreTypes.objectClass)) {
|
|
hasCatchAll = true;
|
|
} else {
|
|
asm.emitPush(exception);
|
|
_genInstanceOf(catchClause.guard);
|
|
|
|
skipCatch = new Label();
|
|
asm.emitJumpIfFalse(skipCatch);
|
|
}
|
|
|
|
_enterScope(catchClause);
|
|
|
|
final exceptionVar = catchClause.exception;
|
|
if (exceptionVar != null) {
|
|
_genPushContextIfCaptured(exceptionVar);
|
|
asm.emitPush(exception);
|
|
_genStoreVar(exceptionVar);
|
|
}
|
|
|
|
final stackTraceVar = catchClause.stackTrace;
|
|
if (stackTraceVar != null) {
|
|
tryBlock.needsStackTrace = true;
|
|
_genPushContextIfCaptured(stackTraceVar);
|
|
asm.emitPush(stackTrace);
|
|
_genStoreVar(stackTraceVar);
|
|
}
|
|
|
|
_generateNode(catchClause.body);
|
|
|
|
_leaveScope();
|
|
asm.emitJump(done);
|
|
|
|
if (skipCatch != null) {
|
|
asm.bind(skipCatch);
|
|
}
|
|
}
|
|
asm.currentSourcePosition = savedSourcePosition;
|
|
|
|
if (!hasCatchAll) {
|
|
tryBlock.needsStackTrace = true;
|
|
_genRethrow(node);
|
|
}
|
|
|
|
asm.bind(done);
|
|
tryCatches.remove(node);
|
|
}
|
|
|
|
@override
|
|
void visitTryFinally(TryFinally node) {
|
|
if (asm.isUnreachable) {
|
|
return;
|
|
}
|
|
|
|
final TryBlock tryBlock = _startTryBlock(node);
|
|
tryBlock.isSynthetic = true;
|
|
final finallyBlocks =
|
|
this.finallyBlocks ??= <TryFinally, List<FinallyBlock>>{};
|
|
finallyBlocks[node] = <FinallyBlock>[];
|
|
|
|
_generateNode(node.body);
|
|
|
|
if (!asm.isUnreachable) {
|
|
final normalContinuation = new FinallyBlock(() {
|
|
/* do nothing (fall through) */
|
|
});
|
|
finallyBlocks[node]!.add(normalContinuation);
|
|
asm.emitJump(normalContinuation.entry);
|
|
}
|
|
|
|
_endTryBlock(node, tryBlock);
|
|
|
|
tryBlock.types.add(cp.addType(const DynamicType()));
|
|
|
|
_generateNode(node.finalizer);
|
|
|
|
tryBlock.needsStackTrace = true; // For rethrowing.
|
|
_genRethrow(node);
|
|
|
|
for (var finallyBlock in finallyBlocks[node]!) {
|
|
asm.bind(finallyBlock.entry);
|
|
_restoreContextForTryBlock(node);
|
|
_generateNode(node.finalizer);
|
|
finallyBlock.generateContinuation();
|
|
}
|
|
|
|
finallyBlocks.remove(node);
|
|
}
|
|
|
|
bool _skipVariableInitialization(VariableDeclaration v, bool isCaptured) {
|
|
// We can skip variable initialization if the variable is supposed to be
|
|
// initialized to null and it's captured. This is because all the slots in
|
|
// the capture context are implicitly initialized to null.
|
|
|
|
// Check if the variable is supposed to be initialized to null.
|
|
if (!(v.initializer == null || v.initializer is NullLiteral)) {
|
|
return false;
|
|
}
|
|
|
|
// Late variables need to be initialized to a sentinel, not null.
|
|
if (v.isLate) return false;
|
|
|
|
// Non-captured variables go in stack slots that aren't implicitly nulled.
|
|
return isCaptured;
|
|
}
|
|
|
|
@override
|
|
void visitVariableDeclaration(VariableDeclaration node) {
|
|
if (!node.isConst) {
|
|
final bool isCaptured = locals.isCaptured(node);
|
|
final initializer = node.initializer;
|
|
final bool emitStore = !_skipVariableInitialization(node, isCaptured);
|
|
int maxInitializerPosition = node.fileOffset;
|
|
if (emitStore) {
|
|
if (isCaptured) {
|
|
_genPushContextForVariable(node);
|
|
}
|
|
if (node.isLate && !_isTrivialInitializer(initializer)) {
|
|
asm.emitPushUninitializedSentinel();
|
|
} else if (initializer != null) {
|
|
_startRecordingMaxPosition(node.fileOffset);
|
|
_generateNode(initializer);
|
|
maxInitializerPosition = _endRecordingMaxPosition();
|
|
} else {
|
|
asm.emitPushNull();
|
|
}
|
|
}
|
|
|
|
if (options.emitDebuggerStops &&
|
|
(initializer == null || _variableSetNeedsDebugCheck(initializer))) {
|
|
final savedSourcePosition = asm.currentSourcePosition;
|
|
if (node.fileEqualsOffset != TreeNode.noOffset) {
|
|
_recordSourcePosition(node.fileEqualsOffset);
|
|
}
|
|
asm.emitDebugCheck();
|
|
asm.currentSourcePosition = savedSourcePosition;
|
|
}
|
|
|
|
if (options.emitLocalVarInfo && !asm.isUnreachable && node.name != null) {
|
|
_declareLocalVariable(node, maxInitializerPosition + 1);
|
|
}
|
|
|
|
if (emitStore) {
|
|
_genStoreVar(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void visitWhileStatement(WhileStatement node) {
|
|
if (asm.isUnreachable) {
|
|
// Bail out before binding a label which allows backward jumps,
|
|
// as it is not handled by local unreachable code elimination.
|
|
return;
|
|
}
|
|
|
|
final Label done = new Label();
|
|
final Label join = new Label(allowsBackwardJumps: true);
|
|
asm.bind(join);
|
|
|
|
asm.emitCheckStack(++currentLoopDepth);
|
|
|
|
_genConditionAndJumpIf(node.condition, false, done);
|
|
|
|
_generateNode(node.body);
|
|
|
|
asm.emitJump(join);
|
|
--currentLoopDepth;
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
@override
|
|
void visitFieldInitializer(FieldInitializer node) {
|
|
_genFieldInitializer(node.field, node.value);
|
|
}
|
|
|
|
@override
|
|
void visitRedirectingInitializer(RedirectingInitializer node) {
|
|
final args = node.arguments;
|
|
assert(args.types.isEmpty);
|
|
_genArguments(new ThisExpression(), args);
|
|
_genDirectCallWithArgs(node.target, args, hasReceiver: true, node: node);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
@override
|
|
void visitSuperInitializer(SuperInitializer node) {
|
|
final args = node.arguments;
|
|
assert(args.types.isEmpty);
|
|
_genArguments(new ThisExpression(), args);
|
|
// Re-resolve target due to partial mixin resolution.
|
|
Member? target;
|
|
for (var replacement in enclosingClass!.superclass!.constructors) {
|
|
if (node.target.name == replacement.name) {
|
|
target = replacement;
|
|
break;
|
|
}
|
|
}
|
|
_genDirectCallWithArgs(target!, args, hasReceiver: true, node: node);
|
|
asm.emitDrop1();
|
|
}
|
|
|
|
@override
|
|
void visitLocalInitializer(LocalInitializer node) {
|
|
_generateNode(node.variable);
|
|
}
|
|
|
|
@override
|
|
void visitAssertInitializer(AssertInitializer node) {
|
|
_generateNode(node.statement);
|
|
}
|
|
|
|
@override
|
|
void visitConstantExpression(ConstantExpression node) {
|
|
_genPushConstant(node.constant);
|
|
}
|
|
|
|
@override
|
|
void visitRecordIndexGet(RecordIndexGet node) {
|
|
_generateNode(node.receiver);
|
|
asm.emitLoadRecordField(node.index);
|
|
}
|
|
|
|
@override
|
|
void visitRecordNameGet(RecordNameGet node) {
|
|
final type = node.receiverType;
|
|
final namedFields = type.named;
|
|
final name = node.name;
|
|
int fieldIndex = -1;
|
|
for (int i = 0; i < namedFields.length; ++i) {
|
|
if (namedFields[i].name == name) {
|
|
fieldIndex = type.positional.length + i;
|
|
break;
|
|
}
|
|
}
|
|
if (fieldIndex < 0) {
|
|
throw 'Unable to find record field "$name" in $type';
|
|
}
|
|
_generateNode(node.receiver);
|
|
asm.emitLoadRecordField(fieldIndex);
|
|
}
|
|
|
|
@override
|
|
void visitRecordLiteral(RecordLiteral node) {
|
|
assert(!node.isConst);
|
|
for (final expr in node.positional) {
|
|
_generateNode(expr);
|
|
}
|
|
for (final expr in node.named) {
|
|
_generateNode(expr.value);
|
|
}
|
|
asm.emitAllocateRecord(cp.addType(node.recordType));
|
|
}
|
|
|
|
@override
|
|
void visitAwaitExpression(AwaitExpression node) {
|
|
_generateNode(node.operand);
|
|
|
|
final int temp = locals.tempIndexInFrame(node);
|
|
asm.emitPopLocal(temp);
|
|
|
|
Label done = Label();
|
|
asm.emitSuspend(done);
|
|
|
|
final runtimeCheckType = node.runtimeCheckType;
|
|
if (runtimeCheckType != null) {
|
|
assert((runtimeCheckType as InterfaceType).classNode ==
|
|
coreTypes.futureClass);
|
|
_genTypeArguments((runtimeCheckType as InterfaceType).typeArguments);
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPush(temp);
|
|
_genDirectCall(
|
|
_awaitWithTypeCheck, objectTable.getArgDescHandle(2, 1), 3);
|
|
} else {
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPush(temp);
|
|
_genDirectCall(_await, objectTable.getArgDescHandle(2), 2);
|
|
}
|
|
asm.emitReturnTOS();
|
|
|
|
asm.bind(done);
|
|
}
|
|
|
|
@override
|
|
void visitYieldStatement(YieldStatement node) {
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
_genDirectCall(
|
|
suspendStateFunctionData, objectTable.getArgDescHandle(1), 1);
|
|
|
|
_generateNode(node.expression);
|
|
|
|
if (enclosingFunction!.dartAsyncMarker == AsyncMarker.AsyncStar) {
|
|
Procedure addMethod = node.isYieldStar
|
|
? asyncStarStreamControllerAddStream
|
|
: asyncStarStreamControllerAdd;
|
|
_genDirectCall(addMethod, objectTable.getArgDescHandle(2), 2);
|
|
|
|
Label normalReturn = Label(allowsBackwardJumps: true);
|
|
asm.emitJumpIfTrue(normalReturn);
|
|
|
|
Label resume = Label();
|
|
asm.emitSuspend(resume);
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPushNull();
|
|
_genDirectCall(yieldAsyncStar, objectTable.getArgDescHandle(2), 2);
|
|
asm.emitReturnTOS();
|
|
|
|
asm.bind(normalReturn);
|
|
final List<TryFinally> tryFinallyBlocks =
|
|
_getEnclosingTryFinallyBlocks(node, null);
|
|
_addFinallyBlocks(tryFinallyBlocks, () {
|
|
asm.emitPush(locals.suspendStateVarIndexInFrame);
|
|
asm.emitPushNull();
|
|
asm.emitStoreLocal(locals.suspendStateVarIndexInFrame);
|
|
_genDirectCall(returnAsyncStar, objectTable.getArgDescHandle(2), 2);
|
|
asm.emitReturnTOS();
|
|
});
|
|
|
|
asm.bind(resume);
|
|
asm.emitJumpIfTrue(normalReturn);
|
|
} else if (enclosingFunction!.dartAsyncMarker == AsyncMarker.SyncStar) {
|
|
Field field = node.isYieldStar
|
|
? syncStarIteratorYieldStarIterable
|
|
: syncStarIteratorCurrent;
|
|
asm.emitStoreFieldTOS(cp.addInstanceField(field));
|
|
|
|
Label done = Label();
|
|
asm.emitSuspend(done);
|
|
asm.emitPushTrue();
|
|
asm.emitReturnTOS();
|
|
|
|
asm.bind(done);
|
|
asm.emitDrop1();
|
|
} else {
|
|
throw 'Unexpected ${enclosingFunction!.dartAsyncMarker}';
|
|
}
|
|
}
|
|
}
|
|
|
|
class UnsupportedOperationError {
|
|
final String message;
|
|
UnsupportedOperationError(this.message);
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|
|
|
|
typedef GenerateContinuation = void Function();
|
|
|
|
class FinallyBlock {
|
|
final Label entry = new Label();
|
|
final GenerateContinuation generateContinuation;
|
|
|
|
FinallyBlock(this.generateContinuation);
|
|
}
|
|
|
|
class Annotations {
|
|
final AnnotationsDeclaration? object;
|
|
final bool hasPragma;
|
|
|
|
const Annotations(this.object, this.hasPragma);
|
|
}
|