diff --git a/pkg/front_end/test/whole_program_test.dart b/pkg/front_end/test/whole_program_test.dart new file mode 100644 index 00000000000..d75f25e5f74 --- /dev/null +++ b/pkg/front_end/test/whole_program_test.dart @@ -0,0 +1,108 @@ +// Copyright (c) 2017, 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:async' show Future; +import 'dart:io' show Directory, File, Platform; + +import 'package:async_helper/async_helper.dart' show asyncEnd, asyncStart; +import 'package:testing/testing.dart' show StdioProcess; + +final Uri compiler = Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'); + +final Uri transform = Uri.base.resolve('pkg/kernel/bin/transform.dart'); +final Uri dump = Uri.base.resolve('pkg/kernel/bin/dump.dart'); + +final Uri packagesFile = Uri.base.resolve('.packages'); + +final Uri dartVm = Uri.base.resolve(Platform.resolvedExecutable); + +Future main() async { + asyncStart(); + final Directory tmp = await Directory.systemTemp.createTemp('whole_program'); + final Uri dartFile = tmp.uri.resolve('hello.dart'); + final Uri dillFile = tmp.uri.resolve('hello.dart.dill'); + final Uri constantsDillFile = tmp.uri.resolve('hello.dart.constants.dill'); + final Uri constantsDillTxtFile = + tmp.uri.resolve('hello.dart.constants.dill.txt'); + + // Write the hello world file. + await new File(dartFile.toFilePath()).writeAsString(''' + // Ensure we import a big program! + import 'package:compiler/src/dart2js.dart'; + import 'package:front_end/src/fasta/kernel/kernel_target.dart'; + + void main() => print('hello world!'); + '''); + + try { + await runCompiler(dartFile, dillFile); + await transformDillFile(dillFile, constantsDillFile); + await dumpDillFile(constantsDillFile, constantsDillTxtFile); + await runHelloWorld(constantsDillFile); + } finally { + await tmp.delete(recursive: true); + } + asyncEnd(); +} + +Future runCompiler(Uri input, Uri output) async { + final buildDir = Uri.base.resolve(Platform.resolvedExecutable).resolve("."); + final platformDill = buildDir.resolve("vm_platform.dill").toFilePath(); + + final List arguments = [ + '--packages=${packagesFile.toFilePath()}', + '-c', + compiler.toFilePath(), + '--platform=$platformDill', + '--output=${output.toFilePath()}', + '--packages=${packagesFile.toFilePath()}', + '--verify', + input.toFilePath(), + ]; + await run('Compilation of hello.dart', arguments); +} + +Future transformDillFile(Uri from, Uri to) async { + final List arguments = [ + transform.toFilePath(), + '-f', + 'bin', + '-t', + 'constants', + '-o', + to.toFilePath(), + from.toFilePath(), + ]; + await run('Transforming $from --to--> $to', arguments); +} + +Future dumpDillFile(Uri dillFile, Uri txtFile) async { + final List arguments = [ + dump.toFilePath(), + dillFile.toFilePath(), + txtFile.toFilePath(), + ]; + await run('Dumping $dillFile --to--> $txtFile', arguments); +} + +Future runHelloWorld(Uri dillFile) async { + final List arguments = ['-c', dillFile.toFilePath()]; + await run('Running hello.dart', arguments, 'hello world!\n'); +} + +Future run(String message, List arguments, + [String expectedOutput]) async { + final Stopwatch sw = new Stopwatch()..start(); + print('Running:\n ${dartVm.toFilePath()} ${arguments.join(' ')}'); + StdioProcess result = await StdioProcess.run(dartVm.toFilePath(), arguments, + timeout: const Duration(seconds: 120)); + print('Output:\n ${result.output.replaceAll('\n', ' \n')}'); + print('ExitCode: ${result.exitCode}'); + print('Took: ${sw.elapsed}\n\n'); + + if ((expectedOutput != null && result.output != expectedOutput) || + result.exitCode != 0) { + throw '$message failed.'; + } +} diff --git a/pkg/kernel/bin/transform.dart b/pkg/kernel/bin/transform.dart index 544e7aae7b4..acd2da22148 100755 --- a/pkg/kernel/bin/transform.dart +++ b/pkg/kernel/bin/transform.dart @@ -13,6 +13,7 @@ import 'package:kernel/kernel.dart'; import 'package:kernel/src/tool/batch_util.dart'; import 'package:kernel/target/targets.dart'; import 'package:kernel/transformations/closure_conversion.dart' as closures; +import 'package:kernel/transformations/constants.dart' as constants; import 'package:kernel/transformations/continuation.dart' as cont; import 'package:kernel/transformations/empty.dart' as empty; import 'package:kernel/transformations/method_call.dart' as method_call; @@ -20,6 +21,7 @@ import 'package:kernel/transformations/mixin_full_resolution.dart' as mix; import 'package:kernel/transformations/treeshaker.dart' as treeshaker; // import 'package:kernel/verifier.dart'; import 'package:kernel/transformations/coq.dart' as coq; +import 'package:kernel/vm/constants_native_effects.dart'; import 'util.dart'; @@ -78,8 +80,9 @@ Future runTransformation(List arguments) async { parseProgramRoots(embedderEntryPointManifests); var program = loadProgramFromBinary(input); - var coreTypes = new CoreTypes(program); - var hierarchy = new ClosedWorldClassHierarchy(program); + + final coreTypes = new CoreTypes(program); + final hierarchy = new ClosedWorldClassHierarchy(program); switch (options['transformation']) { case 'continuation': program = cont.transformProgram(coreTypes, program); @@ -94,6 +97,10 @@ Future runTransformation(List arguments) async { case 'coq': program = coq.transformProgram(coreTypes, program); break; + case 'constants': + final VmConstantsBackend backend = new VmConstantsBackend(coreTypes); + program = constants.transformProgram(program, backend); + break; case 'treeshake': program = treeshaker.transformProgram(coreTypes, hierarchy, program, programRoots: programRoots); diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md index 3d8a1625bf0..823dd93ec94 100644 --- a/pkg/kernel/binary.md +++ b/pkg/kernel/binary.md @@ -78,9 +78,14 @@ type StringReference { UInt index; // Index into the Program's strings. } +type ConstantReference { + UInt index; // Index into the Program's constants. +} + type SourceInfo { List uriUtf8Bytes; List sourceUtf8Bytes; + // Line starts are delta-encoded (they are encoded as line lengths). The list // [0, 10, 25, 32, 42] is encoded as [0, 10, 15, 7, 10]. List lineStarts; @@ -131,6 +136,7 @@ type ProgramFile { List canonicalNames; RList metadataMappings; StringTable strings; + List constants; ProgramIndex programIndex; } @@ -155,6 +161,7 @@ type ProgramIndex { UInt32 binaryOffsetForSourceTable; UInt32 binaryOffsetForCanonicalNames; UInt32 binaryOffsetForStringTable; + UInt32 binaryOffsetForConstantTable; UInt32 mainMethodReference; // This is a ProcedureReference with a fixed-size integer. UInt32[libraryCount + 1] libraryOffsets; UInt32 libraryCount; @@ -810,6 +817,61 @@ type ClosureCreation extends Expression { List typeArguments; } +type ConstantExpression extends Expression { + Byte tag = 107; + ConstantReference constantReference; +} + +abstract type Constant extends Node {} + +type NullConstant extends Constant { + Byte tag = 0; +} + +type BoolConstant extends Constant { + Byte tag = 1; + Byte value; +} + +type IntConstant extends Constant { + Byte tag = 2; + PositiveIntLiteral | NegativeIntLiteral | SpecializedIntLiteral | BigIntLiteral value; +} + +type DoubleConstant extends Constant { + Byte tag = 3; + StringReference value; +} + +type StringConstant extends Constant { + Byte tag = 4; + StringReference value; +} + +type MapConstant extends Constant { + Byte tag = 5; + DartType keyType; + DartType valueType; + List<[ConstantReference, ConstantReference]> keyValueList; +} + +type ListConstant extends Constant { + Byte tag = 6; + DartType type; + List values; +} + +type InstanceConstant extends Constant { + Byte tag = 7; + List typeArguments; + List<[FieldReference, ConstantReference]> values; +} + +type TearOffConstant extends Constant { + Byte tag = 8; + CanonicalNameReference staticProcedureReference; +} + abstract type Statement extends Node {} type InvalidStatement extends Statement { diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart index 9c04a035d64..3afc9fbef4e 100644 --- a/pkg/kernel/lib/ast.dart +++ b/pkg/kernel/lib/ast.dart @@ -3154,6 +3154,28 @@ class FunctionExpression extends Expression { } } +class ConstantExpression extends Expression { + Constant constant; + + ConstantExpression(this.constant) { + assert(constant != null); + } + + DartType getStaticType(TypeEnvironment types) => + throw 'ConstantExpression.staticType() is unimplemented'; + + accept(ExpressionVisitor v) => v.visitConstantExpression(this); + accept1(ExpressionVisitor1 v, arg) => v.visitConstantExpression(this, arg); + + visitChildren(Visitor v) { + constant?.acceptReference(v); + } + + transformChildren(Transformer v) { + constant = v.visitConstant(constant); + } +} + /// Synthetic expression of form `let v = x in y` class Let extends Expression { VariableDeclaration variable; // Must have an initializer. @@ -3444,6 +3466,8 @@ class Block extends Statement { Block(this.statements) { setParents(statements, this); + statements.add(null); + statements.removeLast(); } accept(StatementVisitor v) => v.visitBlock(this); @@ -4900,6 +4924,277 @@ class Supertype extends Node { } } +// ------------------------------------------------------------------------ +// CONSTANTS +// ------------------------------------------------------------------------ + +abstract class Constant extends Node { + /// Calls the `visit*ConstantReference()` method on visitor [v] for all + /// constants referenced in this constant. + /// + /// (Note that a constant can be seen as a DAG (directed acyclic graph) and + /// not a tree!) + visitChildren(Visitor v); + + /// Calls the `visit*Constant()` method on the visitor [v]. + accept(ConstantVisitor v); + + /// Calls the `visit*ConstantReference()` method on the visitor [v]. + acceptReference(Visitor v); + + /// The Kernel AST will reference [Constant]s via [ConstantExpression]s. The + /// constants are not required to be canonicalized, but they have to be deeply + /// comparable via hashCode/==! + int get hashCode; + bool operator ==(Object other); +} + +abstract class PrimitiveConstant extends Constant { + final T value; + + PrimitiveConstant(this.value); + + String toString() => '${this.runtimeType}($value)'; + + int get hashCode => value.hashCode; + + bool operator ==(Object other) => + other is PrimitiveConstant && other.value == value; +} + +class NullConstant extends PrimitiveConstant { + NullConstant() : super(null); + + visitChildren(Visitor v) {} + accept(ConstantVisitor v) => v.visitNullConstant(this); + acceptReference(Visitor v) => v.visitNullConstantReference(this); +} + +class BoolConstant extends PrimitiveConstant { + BoolConstant(bool value) : super(value); + + visitChildren(Visitor v) {} + accept(ConstantVisitor v) => v.visitBoolConstant(this); + acceptReference(Visitor v) => v.visitBoolConstantReference(this); +} + +class IntConstant extends PrimitiveConstant { + IntConstant(int value) : super(value); + + visitChildren(Visitor v) {} + accept(ConstantVisitor v) => v.visitIntConstant(this); + acceptReference(Visitor v) => v.visitIntConstantReference(this); +} + +class DoubleConstant extends PrimitiveConstant { + DoubleConstant(double value) : super(value); + + visitChildren(Visitor v) {} + accept(ConstantVisitor v) => v.visitDoubleConstant(this); + acceptReference(Visitor v) => v.visitDoubleConstantReference(this); + + int get hashCode => value.isNaN ? 199 : super.hashCode; + bool operator ==(Object other) => + other is DoubleConstant && + (other.value == value || identical(value, other.value) /* For NaN */); +} + +class StringConstant extends PrimitiveConstant { + StringConstant(String value) : super(value) { + assert(value != null); + } + + visitChildren(Visitor v) {} + accept(ConstantVisitor v) => v.visitStringConstant(this); + acceptReference(Visitor v) => v.visitStringConstantReference(this); +} + +class MapConstant extends Constant { + final DartType keyType; + final DartType valueType; + final List entries; + + MapConstant(this.keyType, this.valueType, this.entries); + + visitChildren(Visitor v) { + keyType.accept(v); + valueType.accept(v); + for (final ConstantMapEntry entry in entries) { + entry.key.acceptReference(v); + entry.value.acceptReference(v); + } + } + + accept(ConstantVisitor v) => v.visitMapConstant(this); + acceptReference(Visitor v) => v.visitMapConstantReference(this); + + String toString() => '${this.runtimeType}<$keyType, $valueType>($entries)'; + + // TODO(kustermann): Consider combining the hash codes in a better way (also + // below and in [listHashCode]/[mapHashCode]. + int _cachedHashCode; + int get hashCode { + return _cachedHashCode ??= + keyType.hashCode ^ valueType.hashCode ^ listHashCode(entries); + } + + bool operator ==(Object other) => + identical(this, other) || + (other is MapConstant && + other.keyType == keyType && + other.valueType == valueType && + listEquals(other.entries, entries)); +} + +class ConstantMapEntry { + final Constant key; + final Constant value; + ConstantMapEntry(this.key, this.value); + + String toString() => '$key: $value'; + + int get hashCode => key.hashCode ^ value.hashCode; + + bool operator ==(Object other) => + other is ConstantMapEntry && other.key == key && other.value == value; +} + +class ListConstant extends Constant { + final DartType typeArgument; + final List entries; + + ListConstant(this.typeArgument, this.entries); + + visitChildren(Visitor v) { + typeArgument.accept(v); + for (final Constant constant in entries) { + constant.acceptReference(v); + } + } + + accept(ConstantVisitor v) => v.visitListConstant(this); + acceptReference(Visitor v) => v.visitListConstantReference(this); + + String toString() => '${this.runtimeType}<$typeArgument>($entries)'; + + int _cachedHashCode; + int get hashCode { + return _cachedHashCode ??= typeArgument.hashCode ^ listHashCode(entries); + } + + bool operator ==(Object other) => + identical(this, other) || + (other is ListConstant && + other.typeArgument == typeArgument && + listEquals(other.entries, entries)); +} + +class InstanceConstant extends Constant { + final Reference classReference; + final List typeArguments; + final Map fieldValues; + + InstanceConstant(this.classReference, this.typeArguments, this.fieldValues); + + Class get klass => classReference.asClass; + + visitChildren(Visitor v) { + classReference.asClass.acceptReference(v); + visitList(typeArguments, v); + for (final Reference reference in fieldValues.keys) { + reference.asField.acceptReference(v); + } + for (final Constant constant in fieldValues.values) { + constant.acceptReference(v); + } + } + + accept(ConstantVisitor v) => v.visitInstanceConstant(this); + acceptReference(Visitor v) => v.visitInstanceConstantReference(this); + + String toString() { + final sb = new StringBuffer(); + sb.write('${classReference.asClass}'); + if (!classReference.asClass.typeParameters.isEmpty) { + sb.write('<'); + sb.write(typeArguments.map((type) => type.toString()).join(', ')); + sb.write('>'); + } + sb.write(' {'); + fieldValues.forEach((Reference fieldRef, Constant constant) { + sb.write('${fieldRef.asField.name}: $constant, '); + }); + sb.write('}'); + return sb.toString(); + } + + int _cachedHashCode; + int get hashCode { + return _cachedHashCode ??= classReference.hashCode ^ + listHashCode(typeArguments) ^ + mapHashCode(fieldValues); + } + + bool operator ==(Object other) { + return identical(this, other) || + (other is InstanceConstant && + other.classReference == classReference && + listEquals(other.typeArguments, typeArguments) && + mapEquals(other.fieldValues, fieldValues)); + } +} + +class TearOffConstant extends Constant { + final Reference procedureReference; + + TearOffConstant(Procedure procedure) + : procedureReference = procedure.reference { + assert(procedure.isStatic); + } + + TearOffConstant.byReference(this.procedureReference); + + Procedure get procedure => procedureReference?.asProcedure; + + visitChildren(Visitor v) { + procedureReference.asProcedure.acceptReference(v); + } + + accept(ConstantVisitor v) => v.visitTearOffConstant(this); + acceptReference(Visitor v) => v.visitTearOffConstantReference(this); + + String toString() { + return '${runtimeType}(${procedure})'; + } + + int get hashCode => procedure.hashCode; + + bool operator ==(Object other) { + return other is TearOffConstant && other.procedure == procedure; + } +} + +class TypeLiteralConstant extends Constant { + final DartType type; + + TypeLiteralConstant(this.type); + + visitChildren(Visitor v) { + type.accept(v); + } + + accept(ConstantVisitor v) => v.visitTypeLiteralConstant(this); + acceptReference(Visitor v) => v.visitTypeLiteralConstantReference(this); + + String toString() => '${runtimeType}(${type})'; + + int get hashCode => type.hashCode; + + bool operator ==(Object other) { + return other is TypeLiteralConstant && other.type == type; + } +} + // ------------------------------------------------------------------------ // PROGRAM // ------------------------------------------------------------------------ @@ -5251,6 +5546,33 @@ CanonicalName getCanonicalNameOfLibrary(Library library) { return library.canonicalName; } +int listHashCode(List list) { + return list.fold(0, (int value, Object item) => value ^ item.hashCode); +} + +int mapHashCode(Map map) { + int value = 0; + for (final Object x in map.keys) value ^= x.hashCode; + for (final Object x in map.values) value ^= x.hashCode; + return value; +} + +bool listEquals(List a, List b) { + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; + } + return true; +} + +bool mapEquals(Map a, Map b) { + if (a.length != b.length) return false; + for (final Object key in a.keys) { + if (!b.containsKey(key) || a[key] != b[key]) return false; + } + return true; +} + /// Returns the canonical name of [typedef_], or throws an exception if the /// typedef has not been assigned a canonical name yet. /// diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart index 3bb831e556a..80b57beb30b 100644 --- a/pkg/kernel/lib/binary/ast_from_binary.dart +++ b/pkg/kernel/lib/binary/ast_from_binary.dart @@ -25,6 +25,7 @@ class _ProgramIndex { int binaryOffsetForSourceTable; int binaryOffsetForStringTable; int binaryOffsetForCanonicalNames; + int binaryOffsetForConstantTable; int mainMethodReference; List libraryOffsets; int libraryCount; @@ -42,6 +43,7 @@ class BinaryBuilder { int _byteOffset = 0; final List _stringTable = []; final List _sourceUriTable = []; + List _constantTable; List _linkTable; int _transformerFlags = 0; Library _currentLibrary; @@ -150,6 +152,82 @@ class BinaryBuilder { } } + void readConstantTable() { + final int length = readUInt(); + _constantTable = new List(length); + for (int i = 0; i < length; i++) { + _constantTable[i] = readConstantTableEntry(); + } + } + + Constant readConstantTableEntry() { + final int constantTag = readByte(); + switch (constantTag) { + case ConstantTag.NullConstant: + return new NullConstant(); + case ConstantTag.BoolConstant: + return new BoolConstant(readByte() == 1); + case ConstantTag.IntConstant: + return new IntConstant((readExpression() as IntLiteral).value); + case ConstantTag.DoubleConstant: + return new DoubleConstant(double.parse(readStringReference())); + case ConstantTag.StringConstant: + return new StringConstant(readStringReference()); + case ConstantTag.MapConstant: + final DartType keyType = readDartType(); + final DartType valueType = readDartType(); + final int length = readUInt(); + final List entries = + new List(length); + for (int i = 0; i < length; i++) { + final Constant key = readConstantReference(); + final Constant value = readConstantReference(); + entries[i] = new ConstantMapEntry(key, value); + } + return new MapConstant(keyType, valueType, entries); + case ConstantTag.ListConstant: + final DartType typeArgument = readDartType(); + final int length = readUInt(); + final List entries = new List(length); + for (int i = 0; i < length; i++) { + entries[i] = readConstantReference(); + } + return new ListConstant(typeArgument, entries); + case ConstantTag.InstanceConstant: + final Reference classReference = readClassReference(); + final int typeArgumentCount = readUInt(); + final List typeArguments = + new List(typeArgumentCount); + for (int i = 0; i < typeArgumentCount; i++) { + typeArguments[i] = readDartType(); + } + final int fieldValueCount = readUInt(); + final Map fieldValues = {}; + for (int i = 0; i < fieldValueCount; i++) { + final Reference fieldRef = + readCanonicalNameReference().getReference(); + final Constant constant = readConstantReference(); + fieldValues[fieldRef] = constant; + } + return new InstanceConstant(classReference, typeArguments, fieldValues); + case ConstantTag.TearOffConstant: + final Reference reference = readCanonicalNameReference().getReference(); + return new TearOffConstant.byReference(reference); + case ConstantTag.TypeLiteralConstant: + final DartType type = readDartType(); + return new TypeLiteralConstant(type); + } + + throw 'Invalid constant tag $constantTag'; + } + + Constant readConstantReference() { + final int index = readUInt(); + Constant constant = _constantTable[index]; + assert(constant != null); + return constant; + } + String readUriReference() { return _sourceUriTable[readUInt()]; } @@ -340,12 +418,13 @@ class BinaryBuilder { // There are these fields: file size, library count, library count + 1 // offsets, main reference, string table offset, canonical name offset and // source table offset. That's 6 fields + number of libraries. - _byteOffset -= (result.libraryCount + 7) * 4; + _byteOffset -= (result.libraryCount + 8) * 4; // Now read the program index. result.binaryOffsetForSourceTable = _programStartOffset + readUint32(); result.binaryOffsetForCanonicalNames = _programStartOffset + readUint32(); result.binaryOffsetForStringTable = _programStartOffset + readUint32(); + result.binaryOffsetForConstantTable = _programStartOffset + readUint32(); result.mainMethodReference = readUint32(); for (int i = 0; i < result.libraryCount + 1; ++i) { result.libraryOffsets[i] = _programStartOffset + readUint32(); @@ -387,6 +466,9 @@ class BinaryBuilder { Map uriToSource = readUriToSource(); program.uriToSource.addAll(uriToSource); + _byteOffset = index.binaryOffsetForConstantTable; + readConstantTable(); + int numberOfLibraries = index.libraryCount; for (int i = 0; i < numberOfLibraries; ++i) { _byteOffset = index.libraryOffsets[i]; @@ -1245,6 +1327,8 @@ class BinaryBuilder { var typeArgs = readDartTypeList(); return new ClosureCreation.byReference( topLevelFunctionReference, contextVector, functionType, typeArgs); + case Tag.ConstantExpression: + return new ConstantExpression(readConstantReference()); default: throw fail('Invalid expression tag: $tag'); } @@ -1265,7 +1349,8 @@ class BinaryBuilder { List readStatementList() { int length = readUInt(); - List result = new List(length); + List result = []; + result.length = length; for (int i = 0; i < length; ++i) { result[i] = readStatement(); } diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart index 61d205eec44..9fab74089f3 100644 --- a/pkg/kernel/lib/binary/ast_to_binary.dart +++ b/pkg/kernel/lib/binary/ast_to_binary.dart @@ -19,6 +19,7 @@ class BinaryPrinter extends Visitor implements BinarySink { SwitchCaseIndexer _switchCaseIndexer; final TypeParameterIndexer _typeParameterIndexer = new TypeParameterIndexer(); final StringIndexer stringIndexer; + ConstantIndexer _constantIndexer; final StringIndexer _sourceUriIndexer = new StringIndexer(); final Set _knownSourceUri = new Set(); Map _libraryDependencyIndex = @@ -38,6 +39,7 @@ class BinaryPrinter extends Visitor implements BinarySink { int _binaryOffsetForSourceTable = -1; int _binaryOffsetForStringTable = -1; int _binaryOffsetForLinkTable = -1; + int _binaryOffsetForConstantTable = -1; List _canonicalNameList; Set _knownCanonicalNameNonRootTops = new Set(); @@ -53,7 +55,9 @@ class BinaryPrinter extends Visitor implements BinarySink { /// in every printer. BinaryPrinter(Sink> sink, {StringIndexer stringIndexer}) : _sink = new BufferedSink(sink), - stringIndexer = stringIndexer ?? new StringIndexer(); + stringIndexer = stringIndexer ?? new StringIndexer() { + _constantIndexer = new ConstantIndexer(this.stringIndexer); + } void _flush() { _sink.flushAndDestroy(); @@ -93,10 +97,8 @@ class BinaryPrinter extends Visitor implements BinarySink { return _sink.flushedLength + _sink.length; } - void writeStringTable(StringIndexer indexer, bool updateBinaryOffset) { - if (updateBinaryOffset) { - _binaryOffsetForStringTable = getBufferOffset(); - } + void writeStringTable(StringIndexer indexer) { + _binaryOffsetForStringTable = getBufferOffset(); // Write the end offsets. writeUInt30(indexer.numberOfStrings); @@ -119,6 +121,73 @@ class BinaryPrinter extends Visitor implements BinarySink { writeList(strings, writeStringReference); } + void writeConstantReference(Constant constant) { + writeUInt30(_constantIndexer.put(constant)); + } + + void writeConstantTable(ConstantIndexer indexer) { + _binaryOffsetForConstantTable = getBufferOffset(); + + writeUInt30(indexer.entries.length); + for (final entry in indexer.entries) { + writeConstantTableEntry(entry); + } + } + + void writeConstantTableEntry(Constant constant) { + if (constant is NullConstant) { + writeByte(ConstantTag.NullConstant); + } else if (constant is BoolConstant) { + writeByte(ConstantTag.BoolConstant); + writeByte(constant.value ? 1 : 0); + } else if (constant is IntConstant) { + writeByte(ConstantTag.IntConstant); + writeInteger(constant.value); + } else if (constant is DoubleConstant) { + writeByte(ConstantTag.DoubleConstant); + writeStringReference('${constant.value}'); + } else if (constant is StringConstant) { + writeByte(ConstantTag.StringConstant); + writeStringReference(constant.value); + } else if (constant is MapConstant) { + writeByte(ConstantTag.MapConstant); + writeDartType(constant.keyType); + writeDartType(constant.valueType); + writeUInt30(constant.entries.length); + for (final ConstantMapEntry entry in constant.entries) { + writeConstantReference(entry.key); + writeConstantReference(entry.value); + } + } else if (constant is ListConstant) { + writeByte(ConstantTag.ListConstant); + writeDartType(constant.typeArgument); + writeUInt30(constant.entries.length); + constant.entries.forEach(writeConstantReference); + } else if (constant is InstanceConstant) { + writeByte(ConstantTag.InstanceConstant); + writeClassReference(constant.klass); + writeUInt30(constant.typeArguments.length); + constant.typeArguments.forEach(writeDartType); + writeUInt30(constant.fieldValues.length); + constant.fieldValues.forEach((Reference fieldRef, Constant value) { + writeCanonicalNameReference(fieldRef.canonicalName); + writeConstantReference(value); + }); + } else if (constant is TearOffConstant) { + writeByte(ConstantTag.TearOffConstant); + writeCanonicalNameReference(constant.procedure.canonicalName); + } else if (constant is TypeLiteralConstant) { + writeByte(ConstantTag.TypeLiteralConstant); + writeDartType(constant.type); + } else { + throw 'Unsupported constant $constant'; + } + } + + void writeDartType(DartType type) { + type.accept(this); + } + void writeUriReference(String string) { int index = 0; // equivalent to index = _sourceUriIndexer[""]; if (_knownSourceUri.contains(string)) { @@ -220,7 +289,8 @@ class BinaryPrinter extends Visitor implements BinarySink { writeUriToSource(program.uriToSource); writeLinkTable(program); _writeMetadataMappingSection(program); - writeStringTable(stringIndexer, true); + writeStringTable(stringIndexer); + writeConstantTable(_constantIndexer); writeProgramIndex(program, program.libraries); _flush(); @@ -345,6 +415,8 @@ class BinaryPrinter extends Visitor implements BinarySink { writeUInt32(_binaryOffsetForLinkTable); assert(_binaryOffsetForStringTable >= 0); writeUInt32(_binaryOffsetForStringTable); + assert(_binaryOffsetForConstantTable >= 0); + writeUInt32(_binaryOffsetForConstantTable); CanonicalName main = getCanonicalNameOfMember(program.mainMethod); if (main == null) { @@ -974,7 +1046,10 @@ class BinaryPrinter extends Visitor implements BinarySink { } visitIntLiteral(IntLiteral node) { - int value = node.value; + writeInteger(node.value); + } + + writeInteger(int value) { int biasedValue = value + Tag.SpecializedIntLiteralBias; if (biasedValue >= 0 && biasedValue & Tag.SpecializedPayloadMask == biasedValue) { @@ -990,14 +1065,18 @@ class BinaryPrinter extends Visitor implements BinarySink { } else { // TODO: Pick a better format for big int literals. writeByte(Tag.BigIntLiteral); - writeStringReference('${node.value}'); + writeStringReference('$value'); } } visitDoubleLiteral(DoubleLiteral node) { + writeDouble(node.value); + } + + writeDouble(double value) { // TODO: Pick a better format for double literals. writeByte(Tag.DoubleLiteral); - writeStringReference('${node.value}'); + writeStringReference('$value'); } visitBoolLiteral(BoolLiteral node) { @@ -1159,6 +1238,11 @@ class BinaryPrinter extends Visitor implements BinarySink { _labelIndexer.exit(); } + visitConstantExpression(ConstantExpression node) { + writeByte(Tag.ConstantExpression); + writeConstantReference(node.constant); + } + visitBreakStatement(BreakStatement node) { writeByte(Tag.BreakStatement); writeOffset(node.fileOffset); @@ -1414,6 +1498,10 @@ class BinaryPrinter extends Visitor implements BinarySink { writeNode(node.bound); } + defaultConstant(Constant node) { + throw 'Implement handling of ${node.runtimeType}'; + } + defaultNode(Node node) { throw 'Unsupported node: $node'; } @@ -1479,6 +1567,72 @@ class SwitchCaseIndexer { int operator [](SwitchCase node) => index[node]; } +class ConstantIndexer extends RecursiveVisitor { + final StringIndexer stringIndexer; + + final List entries = []; + final Map index = {}; + + ConstantIndexer(this.stringIndexer); + + defaultConstantReference(Constant node) { + put(node); + } + + int put(Constant constant) { + final int value = index[constant]; + if (value != null) return value; + + // Traverse DAG in post-order to ensure children have their id's assigned + // before the parent. + return constant.accept(this); + } + + defaultConstant(Constant node) { + final int oldIndex = index[node]; + if (oldIndex != null) return oldIndex; + + if (node is StringConstant) { + stringIndexer.put(node.value); + } else if (node is DoubleConstant) { + stringIndexer.put('${node.value}'); + } else if (node is IntConstant) { + final int value = node.value; + if ((value.abs() >> 30) != 0) { + stringIndexer.put('$value'); + } + } + + final int newIndex = entries.length; + entries.add(node); + return index[node] = newIndex; + } + + visitMapConstant(MapConstant node) { + for (final ConstantMapEntry entry in node.entries) { + put(entry.key); + put(entry.value); + } + return defaultConstant(node); + } + + visitListConstant(ListConstant node) { + for (final Constant entry in node.entries) { + put(entry); + } + return defaultConstant(node); + } + + visitInstanceConstant(InstanceConstant node) { + for (final Constant entry in node.fieldValues.values) { + put(entry); + } + return defaultConstant(node); + } + + int operator [](Constant node) => index[node]; +} + class TypeParameterIndexer { final Map index = {}; int stackHeight = 0; diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart index c46e24406bf..6ece984b8b1 100644 --- a/pkg/kernel/lib/binary/tag.dart +++ b/pkg/kernel/lib/binary/tag.dart @@ -113,6 +113,8 @@ class Tag { static const int ClosureCreation = 106; + static const int ConstantExpression = 107; + static const int SpecializedTagHighBit = 0x80; // 10000000 static const int SpecializedTagMask = 0xF8; // 11111000 static const int SpecializedPayloadMask = 0x7; // 00000111 @@ -130,3 +132,16 @@ class Tag { /// Keep in sync with runtime/vm/kernel_binary.h. static const int BinaryFormatVersion = 1; } + +abstract class ConstantTag { + static const int NullConstant = 0; + static const int BoolConstant = 1; + static const int IntConstant = 2; + static const int DoubleConstant = 3; + static const int StringConstant = 4; + static const int MapConstant = 5; + static const int ListConstant = 6; + static const int InstanceConstant = 7; + static const int TearOffConstant = 8; + static const int TypeLiteralConstant = 9; +} diff --git a/pkg/kernel/lib/clone.dart b/pkg/kernel/lib/clone.dart index 74b1fc14e8b..366a29d6ea1 100644 --- a/pkg/kernel/lib/clone.dart +++ b/pkg/kernel/lib/clone.dart @@ -52,6 +52,10 @@ class CloneVisitor extends TreeVisitor { return substitute(type, typeSubstitution); } + Constant visitConstant(Constant constant) { + return constant; + } + DartType visitOptionalType(DartType type) { return type == null ? null : substitute(type, typeSubstitution); } @@ -202,6 +206,10 @@ class CloneVisitor extends TreeVisitor { return new FunctionExpression(clone(node.function)); } + visitConstantExpression(ConstantExpression node) { + return new ConstantExpression(visitConstant(node.constant)); + } + visitStringLiteral(StringLiteral node) { return new StringLiteral(node.value); } diff --git a/pkg/kernel/lib/text/ast_to_text.dart b/pkg/kernel/lib/text/ast_to_text.dart index 60f75a7813a..04630e398c6 100644 --- a/pkg/kernel/lib/text/ast_to_text.dart +++ b/pkg/kernel/lib/text/ast_to_text.dart @@ -6,14 +6,35 @@ library kernel.ast_to_text; import '../ast.dart'; import '../import_table.dart'; -class Namer { +abstract class Namer { int index = 0; - final String prefix; final Map map = {}; - Namer(this.prefix); - String getName(T key) => map.putIfAbsent(key, () => '$prefix${++index}'); + + String get prefix; +} + +class NormalNamer extends Namer { + final String prefix; + NormalNamer(this.prefix); +} + +class ConstantNamer extends RecursiveVisitor with Namer { + final String prefix; + ConstantNamer(this.prefix); + + String getName(Constant constant) { + if (!map.containsKey(constant)) { + // Name everything in post-order visit of DAG. + constant.visitChildren(this); + } + return super.getName(constant); + } + + defaultConstantReference(Constant constant) { + getName(constant); + } } class Disambiguator { @@ -103,12 +124,14 @@ String programToString(Program node) { class NameSystem { final Namer variables = - new Namer('#t'); - final Namer members = new Namer('#m'); - final Namer classes = new Namer('#class'); - final Namer libraries = new Namer('#lib'); - final Namer typeParameters = new Namer('#T'); - final Namer labels = new Namer('#L'); + new NormalNamer('#t'); + final Namer members = new NormalNamer('#m'); + final Namer classes = new NormalNamer('#class'); + final Namer libraries = new NormalNamer('#lib'); + final Namer typeParameters = + new NormalNamer('#T'); + final Namer labels = new NormalNamer('#L'); + final Namer constants = new ConstantNamer('#C'); final Disambiguator prefixes = new Disambiguator(); nameVariable(VariableDeclaration node) => variables.getName(node); @@ -118,6 +141,7 @@ class NameSystem { nameTypeParameter(TypeParameter node) => typeParameters.getName(node); nameSwitchCase(SwitchCase node) => labels.getName(node); nameLabeledStatement(LabeledStatement node) => labels.getName(node); + nameConstant(Constant node) => constants.getName(node); nameLibraryPrefix(Library node, {String proposedName}) { return prefixes.disambiguate(node, () { @@ -369,6 +393,15 @@ class Printer extends Visitor { --inner.indentation; endLine('}'); } + writeWord('constants '); + endLine(' {'); + ++inner.indentation; + for (final Constant constant + in syntheticNames.constants.map.keys.toList()) { + inner.writeNode(constant); + } + --inner.indentation; + endLine('}'); } int getPrecedence(TreeNode node) { @@ -1646,6 +1679,55 @@ class Printer extends Visitor { writeType(node.bound); } + visitConstantExpression(ConstantExpression node) { + writeWord(syntheticNames.nameConstant(node.constant)); + } + + defaultConstant(Constant node) { + final String name = syntheticNames.nameConstant(node); + endLine(' $name = $node'); + } + + visitListConstant(ListConstant node) { + final String name = syntheticNames.nameConstant(node); + write(' $name = '); + final String entries = node.entries.map((Constant constant) { + return syntheticNames.nameConstant(constant); + }).join(', '); + endLine('${node.runtimeType}<${node.typeArgument}>($entries)'); + } + + visitMapConstant(MapConstant node) { + final String name = syntheticNames.nameConstant(node); + write(' $name = '); + final String entries = node.entries.map((ConstantMapEntry entry) { + final String key = syntheticNames.nameConstant(entry.key); + final String value = syntheticNames.nameConstant(entry.value); + return '$key: $value'; + }).join(', '); + endLine( + '${node.runtimeType}<${node.keyType}, ${node.valueType}>($entries)'); + } + + visitInstanceConstant(InstanceConstant node) { + final String name = syntheticNames.nameConstant(node); + write(' $name = '); + final sb = new StringBuffer(); + sb.write('${node.klass}'); + if (!node.klass.typeParameters.isEmpty) { + sb.write('<'); + sb.write(node.typeArguments.map((type) => type.toString()).join(', ')); + sb.write('>'); + } + sb.write(' {'); + node.fieldValues.forEach((Reference fieldRef, Constant constant) { + final String name = syntheticNames.nameConstant(constant); + sb.write('${fieldRef.asField.name}: $name, '); + }); + sb.write('}'); + endLine(sb.toString()); + } + defaultNode(Node node) { write('<${node.runtimeType}>'); } diff --git a/pkg/kernel/lib/transformations/constants.dart b/pkg/kernel/lib/transformations/constants.dart new file mode 100644 index 00000000000..9a870698269 --- /dev/null +++ b/pkg/kernel/lib/transformations/constants.dart @@ -0,0 +1,813 @@ +// Copyright (c) 2017, 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. + +library kernel.transformations.constants; + +import '../kernel.dart'; +import '../ast.dart'; +import '../type_algebra.dart'; +import 'treeshaker.dart' show findNativeName; + +Program transformProgram(Program program, ConstantsBackend backend, + {bool keepFields: false}) { + transformLibraries(program.libraries, backend, keepFields: keepFields); + return program; +} + +void transformLibraries(List libraries, ConstantsBackend backend, + {bool keepFields: false, bool keepVariables: false}) { + final ConstantsTransformer constantsTransformer = + new ConstantsTransformer(backend, keepFields, keepVariables); + for (final Library library in libraries) { + for (final Field field in library.fields.toList()) { + constantsTransformer.convertField(field); + } + for (final Procedure procedure in library.procedures) { + constantsTransformer.convertProcedure(procedure); + } + for (final Class klass in library.classes) { + constantsTransformer.convertClassAnnotations(klass); + + for (final Field field in klass.fields.toList()) { + constantsTransformer.convertField(field); + } + for (final Procedure procedure in klass.procedures) { + constantsTransformer.convertProcedure(procedure); + } + for (final Constructor constructor in klass.constructors) { + constantsTransformer.convertConstructor(constructor); + } + } + } +} + +class ConstantsTransformer extends Transformer { + final ConstantEvaluator constantEvaluator; + + /// Whether to preserve constant [Field]s. All use-sites will be rewritten. + final bool keepFields; + final bool keepVariables; + + ConstantsTransformer( + ConstantsBackend backend, this.keepFields, this.keepVariables) + : constantEvaluator = new ConstantEvaluator(backend); + + // Transform the library/class members: + + void convertClassAnnotations(Class klass) { + constantEvaluator.withNewEnvironment(() { + transformList(klass.annotations, this, klass); + }); + } + + void convertProcedure(Procedure procedure) { + constantEvaluator.withNewEnvironment(() { + procedure.accept(this); + }); + } + + void convertConstructor(Constructor constructor) { + constantEvaluator.withNewEnvironment(() { + constructor.accept(this); + }); + } + + void convertField(Field field) { + constantEvaluator.withNewEnvironment(() { + if (field.accept(this) == null) field.remove(); + }); + } + + // Handle definition of constants: + + visitVariableDeclaration(VariableDeclaration node) { + if (node.isConst) { + final Constant constant = constantEvaluator.evaluate(node.initializer); + constantEvaluator.env.addVariableValue(node, constant); + + if (keepVariables) { + // So the value of the variable is still available for debugging + // purposes we convert the constant variable to be a final variable + // initialized to the evaluated constant expression. + node.initializer = new ConstantExpression(constant)..parent = node; + node.isFinal = true; + node.isConst = false; + } else { + // Since we convert all use-sites of constants, the constant + // [VariableDeclaration] is unused and we'll therefore remove it. + return null; + } + } + return super.visitVariableDeclaration(node); + } + + visitField(Field node) { + if (node.isConst) { + // Since we convert all use-sites of constants, the constant [Field] + // cannot be referenced anymore. We therefore get rid of it if + // [keepFields] was not specified. + if (!keepFields) return null; + + // Otherwise we keep the constant [Field] and convert it's initializer. + if (node.initializer != null) { + final Constant constant = constantEvaluator.evaluate(node.initializer); + node.initializer = new ConstantExpression(constant)..parent = node; + } + return node; + } + return super.visitField(node); + } + + // Handle use-sites of constants (and "inline" constant expressions): + + visitStaticGet(StaticGet node) { + final Member target = node.target; + if (target is Field && target.isConst) { + final Constant constant = constantEvaluator.evaluate(target.initializer); + return new ConstantExpression(constant); + } else if (target is Procedure && target.kind == ProcedureKind.Method) { + final Constant constant = constantEvaluator.evaluate(node); + return new ConstantExpression(constant); + } + return super.visitStaticGet(node); + } + + visitVariableGet(VariableGet node) { + if (node.variable.isConst) { + final Constant constant = + constantEvaluator.evaluate(node.variable.initializer); + return new ConstantExpression(constant); + } + return super.visitVariableGet(node); + } + + visitListLiteral(ListLiteral node) { + if (node.isConst) { + return new ConstantExpression(constantEvaluator.evaluate(node)); + } + return super.visitListLiteral(node); + } + + visitMapLiteral(MapLiteral node) { + if (node.isConst) { + return new ConstantExpression(constantEvaluator.evaluate(node)); + } + return super.visitMapLiteral(node); + } + + visitConstructorInvocation(ConstructorInvocation node) { + if (node.isConst) { + return new ConstantExpression(constantEvaluator.evaluate(node)); + } + return super.visitConstructorInvocation(node); + } +} + +class ConstantEvaluator extends RecursiveVisitor { + final ConstantsBackend backend; + + final Map canonicalizationCache; + final Map nodeCache; + + final NullConstant nullConstant = new NullConstant(); + final BoolConstant trueConstant = new BoolConstant(true); + final BoolConstant falseConstant = new BoolConstant(false); + + InstanceBuilder instanceBuilder; + EvaluationEnvironment env; + + ConstantEvaluator(this.backend) + : canonicalizationCache = {}, + nodeCache = {}; + + /// Evaluates [node] and possibly cache the evaluation result. + Constant evaluate(Expression node) { + if (node == null) return nullConstant; + if (env.isEmpty) { + return nodeCache.putIfAbsent(node, () => node.accept(this)); + } + return node.accept(this); + } + + defaultTreeNode(Node node) { + // Only a subset of the expression language is valid for constant + // evaluation. + throw new ConstantEvaluationError( + 'Constant evaluation has no support for ${node.runtimeType} yet!'); + } + + visitNullLiteral(NullLiteral node) => nullConstant; + + visitBoolLiteral(BoolLiteral node) { + return node.value ? trueConstant : falseConstant; + } + + visitIntLiteral(IntLiteral node) { + return canonicalize(new IntConstant(node.value)); + } + + visitDoubleLiteral(DoubleLiteral node) { + return canonicalize(new DoubleConstant(node.value)); + } + + visitStringLiteral(StringLiteral node) { + return canonicalize(new StringConstant(node.value)); + } + + visitTypeLiteral(TypeLiteral node) { + final DartType type = evaluateDartType(node.type); + return canonicalize(new TypeLiteralConstant(type)); + } + + visitConstantExpression(ConstantExpression node) { + // If there were already constants in the AST then we make sure we + // re-canonicalize them. After running the transformer we will therefore + // have a fully-canonicalized constant DAG with roots coming from the + // [ConstantExpression] nodes in the AST. + return canonicalize(node.constant); + } + + visitListLiteral(ListLiteral node) { + final List entries = new List(node.expressions.length); + for (int i = 0; i < node.expressions.length; ++i) { + entries[i] = node.expressions[i].accept(this); + } + final DartType typeArgument = evaluateDartType(node.typeArgument); + final ListConstant listConstant = new ListConstant(typeArgument, entries); + return canonicalize(backend.lowerListConstant(listConstant)); + } + + visitMapLiteral(MapLiteral node) { + final Set usedKeys = new Set(); + final List entries = + new List(node.entries.length); + for (int i = 0; i < node.entries.length; ++i) { + final key = node.entries[i].key.accept(this); + final value = node.entries[i].value.accept(this); + if (!usedKeys.add(key)) { + throw new ConstantEvaluationError( + 'Duplicate key "$key" in constant map literal.'); + } + entries[i] = new ConstantMapEntry(key, value); + } + final DartType keyType = evaluateDartType(node.keyType); + final DartType valueType = evaluateDartType(node.valueType); + final MapConstant mapConstant = + new MapConstant(keyType, valueType, entries); + return canonicalize(backend.lowerMapConstant(mapConstant)); + } + + visitConstructorInvocation(ConstructorInvocation node) { + final Constructor constructor = node.target; + final Class klass = constructor.enclosingClass; + + final typeArguments = evaluateTypeArguments(node.arguments); + final positionals = evaluatePositionalArguments(node.arguments); + final named = evaluateNamedArguments(node.arguments); + + // Fill in any missing type arguments with "dynamic". + for (int i = typeArguments.length; i < klass.typeParameters.length; i++) { + typeArguments.add(const DynamicType()); + } + + // Start building a new instance. + return withNewInstanceBuilder(klass, typeArguments, () { + // "Run" the constructor (and any super constructor calls), which will + // initialize the fields of the new instance. + handleConstructorInvocation( + constructor, typeArguments, positionals, named); + return canonicalize(instanceBuilder.buildInstance()); + }); + } + + void handleConstructorInvocation( + Constructor constructor, + List typeArguments, + List positionalArguments, + Map namedArguments) { + return withNewEnvironment(() { + final Class klass = constructor.enclosingClass; + final FunctionNode function = constructor.function; + + // We simulate now the constructor invocation. + + // Step 1) Map type arguments and normal arguments from caller to callee. + for (int i = 0; i < klass.typeParameters.length; i++) { + env.addTypeParameterValue(klass.typeParameters[i], typeArguments[i]); + } + for (int i = 0; i < function.positionalParameters.length; i++) { + final VariableDeclaration parameter = function.positionalParameters[i]; + final Constant value = (i < positionalArguments.length) + ? positionalArguments[i] + : evaluate(parameter.initializer); + env.addVariableValue(parameter, value); + } + for (final VariableDeclaration parameter in function.namedParameters) { + final Constant value = + namedArguments[parameter.name] ?? evaluate(parameter.initializer); + env.addVariableValue(parameter, value); + } + + // Step 2) Run all initializers (including super calls) with environment setup. + for (final Field field in klass.fields) { + if (!field.isStatic) { + instanceBuilder.setFieldValue(field, evaluate(field.initializer)); + } + } + for (final Initializer init in constructor.initializers) { + if (init is FieldInitializer) { + instanceBuilder.setFieldValue(init.field, evaluate(init.value)); + } else if (init is LocalInitializer) { + final VariableDeclaration variable = init.variable; + env.addVariableValue(variable, evaluate(variable.initializer)); + } else if (init is SuperInitializer) { + handleConstructorInvocation( + init.target, + evaluateSuperTypeArguments(constructor.enclosingClass.supertype), + evaluatePositionalArguments(init.arguments), + evaluateNamedArguments(init.arguments)); + } else if (init is RedirectingInitializer) { + // Since a redirecting constructor targets a constructor of the same + // class, we pass the same [typeArguments]. + handleConstructorInvocation( + init.target, + typeArguments, + evaluatePositionalArguments(init.arguments), + evaluateNamedArguments(init.arguments)); + } else { + throw new ConstantEvaluationError( + 'Cannot evaluate constant with [${init.runtimeType}].'); + } + } + }); + } + + visitMethodInvocation(MethodInvocation node) { + // We have no support for generic method invocation atm. + assert(node.arguments.named.isEmpty); + + final Constant receiver = evaluate(node.receiver); + final List arguments = + evaluatePositionalArguments(node.arguments); + + // Handle == and != first (it's common between all types). + if (arguments.length == 1 && node.name.name == '==') { + ensurePrimitiveConstant(receiver); + final right = arguments[0]; + ensurePrimitiveConstant(right); + return receiver == right ? trueConstant : falseConstant; + } + if (arguments.length == 1 && node.name.name == '!=') { + ensurePrimitiveConstant(receiver); + final right = arguments[0]; + ensurePrimitiveConstant(right); + return receiver != right ? trueConstant : falseConstant; + } + + // This is a white-listed set of methods we need to support on constants. + if (receiver is StringConstant) { + if (arguments.length == 1) { + switch (node.name.name) { + case '+': + final StringConstant other = arguments[0]; + return canonicalize( + new StringConstant(receiver.value + other.value)); + } + } + } else if (receiver is BoolConstant) { + if (arguments.length == 1) { + switch (node.name.name) { + case '!': + return !receiver.value ? trueConstant : falseConstant; + } + } else if (arguments.length == 2) { + final right = arguments[0]; + if (right is BoolConstant) { + switch (node.name.name) { + case '&&': + return (receiver.value && right.value) + ? trueConstant + : falseConstant; + case '||': + return (receiver.value || right.value) + ? trueConstant + : falseConstant; + } + } + throw new ConstantEvaluationError( + 'Method "${node.name}" is only allowed with boolean arguments.'); + } + } else if (receiver is IntConstant) { + if (arguments.length == 0) { + switch (node.name.name) { + case 'unary-': + return canonicalize(new IntConstant(-receiver.value)); + case '~': + return canonicalize(new IntConstant(~receiver.value)); + } + } else if (arguments.length == 1) { + final Constant other = arguments[0]; + if (other is IntConstant) { + switch (node.name.name) { + case '|': + return canonicalize( + new IntConstant(receiver.value | other.value)); + case '&': + return canonicalize( + new IntConstant(receiver.value & other.value)); + case '^': + return canonicalize( + new IntConstant(receiver.value ^ other.value)); + case '<<': + return canonicalize( + new IntConstant(receiver.value << other.value)); + case '>>': + return canonicalize( + new IntConstant(receiver.value >> other.value)); + } + } + + if (other is IntConstant || other is DoubleConstant) { + final num value = (other is IntConstant) + ? other.value + : (other as DoubleConstant).value; + return evaluateBinaryNumericOperation( + node.name.name, receiver.value, value); + } + } + } else if (receiver is DoubleConstant) { + if (arguments.length == 0) { + switch (node.name.name) { + case 'unary-': + return canonicalize(new DoubleConstant(-receiver.value)); + } + } else if (arguments.length == 1) { + final Constant other = arguments[0]; + + if (other is IntConstant || other is DoubleConstant) { + final num value = (other is IntConstant) + ? other.value + : (other as DoubleConstant).value; + return evaluateBinaryNumericOperation( + node.name.name, receiver.value, value); + } + } + } + + throw new ConstantEvaluationError( + 'Cannot evaluate general method invocation: ' + 'receiver: $receiver, method: ${node.name}, arguments: $arguments!'); + } + + visitLogicalExpression(LogicalExpression node) { + final Constant left = evaluate(node.left); + switch (node.operator) { + case '||': + if (left is BoolConstant) { + if (left.value) return trueConstant; + + final Constant right = evaluate(node.right); + if (right is BoolConstant) { + return right; + } + throw new ConstantEvaluationError( + '"$right" is not bool constant and is disallowed with "||".'); + } + throw new ConstantEvaluationError( + '"$left" is not bool constant and is disallowed with "||".'); + case '&&': + if (left is BoolConstant) { + if (!left.value) return falseConstant; + + final Constant right = evaluate(node.right); + if (right is BoolConstant) { + return right; + } + throw new ConstantEvaluationError( + '"$right" is not bool constant and is disallowed with "&&".'); + } + throw new ConstantEvaluationError( + '"$left" is not bool constant and is disallowed with "&&".'); + case '??': + return (left is! NullConstant) ? left : evaluate(node.right); + default: + throw new ConstantEvaluationError( + 'No support for logical operator ${node.operator}.'); + } + } + + visitConditionalExpression(ConditionalExpression node) { + final BoolConstant constant = evaluate(node.condition); + if (constant == trueConstant) { + return evaluate(node.then); + } else { + assert(constant == falseConstant); + return evaluate(node.otherwise); + } + } + + visitPropertyGet(PropertyGet node) { + if (node.receiver is ThisExpression) { + // Access "this" during instance creation. + for (final Field field in instanceBuilder.fields.keys) { + if (field.name == node.name) { + return instanceBuilder.fields[field]; + } + } + throw 'Could not evaluate field get ${node.name} on incomplete instance'; + } + + final Constant receiver = evaluate(node.receiver); + if (receiver is StringConstant && node.name.name == 'length') { + return canonicalize(new IntConstant(receiver.value.length)); + } else if (receiver is InstanceConstant) { + for (final Reference fieldRef in receiver.fieldValues.keys) { + if (fieldRef.asField.name == node.name) { + return receiver.fieldValues[fieldRef]; + } + } + } + + throw 'Could not evaluate property get on $receiver.'; + } + + visitLet(Let node) { + env.addVariableValue(node.variable, evaluate(node.variable.initializer)); + return node.body.accept(this); + } + + visitVariableGet(VariableGet node) { + return env.lookupVariable(node.variable); + } + + visitStaticGet(StaticGet node) { + return withNewEnvironment(() { + final Member target = node.target; + if (target is Field && target.isConst) { + return evaluate(target.initializer); + } else if (target is Procedure) { + return canonicalize(new TearOffConstant(target)); + } + throw 'Could not handle static get of $target.'; + }); + } + + visitStringConcatenation(StringConcatenation node) { + final String value = node.expressions.map((Expression node) { + final Constant constant = node.accept(this); + + if (constant is NullConstant) { + return 'null'; + } else if (constant is BoolConstant) { + return constant.value ? 'true' : 'false'; + } else if (constant is IntConstant) { + return constant.value.toString(); + } else if (constant is DoubleConstant) { + return constant.value.toString(); + } else if (constant is StringConstant) { + return constant.value; + } else { + throw new ConstantEvaluationError( + 'Only null/bool/int/double/String values are allowed as string ' + 'interpolation expressions during constant evaluation.'); + } + }).join(''); + return canonicalize(new StringConstant(value)); + } + + visitStaticInvocation(StaticInvocation node) { + final Member target = node.target; + if (target is Procedure) { + if (target.kind == ProcedureKind.Factory) { + final String nativeName = findNativeName(target); + if (nativeName != null) { + final Constant constant = backend.buildConstantForNative( + nativeName, + evaluateTypeArguments(node.arguments), + evaluatePositionalArguments(node.arguments), + evaluateNamedArguments(node.arguments)); + assert(constant != null); + return canonicalize(constant); + } + } else if (target.name.name == 'identical') { + // Ensure the "identical()" function comes from dart:core. + final parent = target.parent; + if (parent is Library && parent.importUri == 'dart:core') { + final positionalArguments = + evaluatePositionalArguments(node.arguments); + final left = positionalArguments[0]; + ensurePrimitiveConstant(left); + final right = positionalArguments[1]; + ensurePrimitiveConstant(right); + return left == right ? trueConstant : falseConstant; + } + } + } + + throw new ConstantEvaluationError( + 'Calling "$target" during constant evaluation is disallowed.'); + } + + // Helper methods: + + List evaluateTypeArguments(Arguments arguments) { + return evaluateDartTypes(arguments.types); + } + + List evaluateSuperTypeArguments(Supertype type) { + return evaluateDartTypes(type.typeArguments); + } + + List evaluateDartTypes(List types) { + if (env.isEmpty) return types; + return types.map(evaluateDartType).toList(); + } + + DartType evaluateDartType(DartType type) { + return env.subsituteType(type); + } + + List evaluatePositionalArguments(Arguments arguments) { + return arguments.positional.map((Expression node) { + return node.accept(this); + }).toList(); + } + + Map evaluateNamedArguments(Arguments arguments) { + if (arguments.named.isEmpty) return const {}; + + final Map named = {}; + arguments.named.forEach((NamedExpression pair) { + named[pair.name] = pair.value.accept(this); + }); + return named; + } + + canonicalize(Constant constant) { + return canonicalizationCache.putIfAbsent(constant, () => constant); + } + + withNewInstanceBuilder(Class klass, List typeArguments, fn()) { + InstanceBuilder old = instanceBuilder; + instanceBuilder = new InstanceBuilder(klass, typeArguments); + final result = fn(); + instanceBuilder = old; + return result; + } + + withNewEnvironment(fn()) { + final EvaluationEnvironment oldEnv = env; + env = new EvaluationEnvironment(); + final result = fn(); + env = oldEnv; + return result; + } + + ensurePrimitiveConstant(Constant value) { + if (value is! NullConstant && + value is! BoolConstant && + value is! IntConstant && + value is! DoubleConstant && + value is! StringConstant) { + throw new ConstantEvaluationError( + '"$value" is not a primitive constant (null/bool/int/double/string) ' + ' and is disallowed in this context.'); + } + } + + evaluateBinaryNumericOperation(String op, num a, num b) { + num result; + switch (op) { + case '+': + result = a + b; + break; + case '-': + result = a - b; + break; + case '*': + result = a - b; + break; + case '/': + result = a / b; + break; + case '~/': + result = a ~/ b; + break; + case '%': + result = a ~/ b; + break; + } + + if (result != null) { + return canonicalize(result is int + ? new IntConstant(result) + : new DoubleConstant(result as double)); + } + + switch (op) { + case '<': + return a < b ? trueConstant : falseConstant; + case '<=': + return a <= b ? trueConstant : falseConstant; + case '>=': + return a >= b ? trueConstant : falseConstant; + case '>': + return a > b ? trueConstant : falseConstant; + } + + throw new ConstantEvaluationError( + 'Binary operation "$op" on num is disallowed.'); + } +} + +/// Holds the necessary information for a constant object, namely +/// * the [klass] being instantiated +/// * the [typeArguments] used for the instantiation +/// * the [fields] the instance will obtain (all fields from the +/// instantiated [klass] up to the [Object] klass). +class InstanceBuilder { + /// The class of the new instance. + final Class klass; + + /// The values of the type parameters of the new instance. + final List typeArguments; + + /// The field values of the new instance. + final Map fields = {}; + + InstanceBuilder(this.klass, this.typeArguments); + + void setFieldValue(Field field, Constant constant) { + fields[field] = constant; + } + + InstanceConstant buildInstance() { + final Map fieldValues = {}; + fields.forEach((Field field, Constant value) { + fieldValues[field.reference] = value; + }); + return new InstanceConstant(klass.reference, typeArguments, fieldValues); + } +} + +/// Holds an environment of type parameters, parameters and variables. +class EvaluationEnvironment { + /// The values of the type parameters in scope. + final Map _typeVariables = + {}; + + /// The values of the parameters/variables in scope. + final Map _variables = + {}; + + /// Whether the current environment is empty. + bool get isEmpty => _typeVariables.isEmpty && _variables.isEmpty; + + void addTypeParameterValue(TypeParameter parameter, DartType value) { + assert(!_typeVariables.containsKey(parameter)); + _typeVariables[parameter] = value; + } + + void addVariableValue(VariableDeclaration variable, Constant value) { + assert(!_variables.containsKey(variable)); + _variables[variable] = value; + } + + DartType lookupParameterValue(TypeParameter parameter) { + final DartType value = _typeVariables[parameter]; + assert(value != null); + return value; + } + + Constant lookupVariable(VariableDeclaration variable) { + final Constant value = _variables[variable]; + assert(value != null); + return value; + } + + DartType subsituteType(DartType type) { + if (_typeVariables.isEmpty) return type; + return substitute(type, _typeVariables); + } +} + +abstract class ConstantsBackend { + Constant buildConstantForNative( + String nativeName, + List typeArguments, + List positionalArguments, + Map namedArguments); + + Constant lowerListConstant(ListConstant constant); + Constant lowerMapConstant(MapConstant constant); +} + +/// Represents a compile-time error reported during constant evaluation. +class ConstantEvaluationError { + final String message; + + ConstantEvaluationError(this.message); + + String toString() => 'Error during constant evaluation: $message'; +} diff --git a/pkg/kernel/lib/transformations/treeshaker.dart b/pkg/kernel/lib/transformations/treeshaker.dart index 60846e4c4ae..1c53723c390 100644 --- a/pkg/kernel/lib/transformations/treeshaker.dart +++ b/pkg/kernel/lib/transformations/treeshaker.dart @@ -1087,3 +1087,28 @@ class _ExternalTypeVisitor extends DartTypeVisitor { /// Exception that is thrown to stop the tree shaking analysis when a use /// of `dart:mirrors` is found. class _UsingMirrorsException {} + +String findNativeName(Member procedure) { + // Native procedures are marked as external and have an annotation, + // which looks like this: + // + // import 'dart:_internal' as internal; + // + // @internal.ExternalName("") + // external Object foo(arg0, ...); + // + if (procedure.isExternal) { + for (final Expression annotation in procedure.annotations) { + if (annotation is ConstructorInvocation) { + final Class klass = annotation.target.enclosingClass; + if (klass.name == 'ExternalName' && + klass.enclosingLibrary.importUri.toString() == 'dart:_internal') { + assert(annotation.arguments.positional.length == 1); + return (annotation.arguments.positional[0] as StringLiteral).value; + } + } + } + throw 'External procedure has no @ExternalName("...") annotation!'; + } + return null; +} diff --git a/pkg/kernel/lib/type_checker.dart b/pkg/kernel/lib/type_checker.dart index 6421efcb802..cddbe310431 100644 --- a/pkg/kernel/lib/type_checker.dart +++ b/pkg/kernel/lib/type_checker.dart @@ -995,4 +995,11 @@ class TypeCheckingVisitor @override visitInvalidInitializer(InvalidInitializer node) {} + + @override + visitConstantExpression(ConstantExpression node) { + // Without explicitly running the "constants" transformation, we should + // never get here! + throw 'unreachable'; + } } diff --git a/pkg/kernel/lib/verifier.dart b/pkg/kernel/lib/verifier.dart index be3697eae88..cea0972051b 100644 --- a/pkg/kernel/lib/verifier.dart +++ b/pkg/kernel/lib/verifier.dart @@ -382,7 +382,15 @@ class VerifyingVisitor extends RecursiveVisitor { if (node.target == null) { problem(node, "StaticGet without target."); } - if (!node.target.hasGetter) { + // Currently Constructor.hasGetter returns `false` even though fasta uses it + // as a getter for internal purposes: + // + // Fasta is letting all call site of a redirecting constructor be resolved + // to the real target. In order to resolve it, it seems to add a body into + // the redirecting-factory constructor which caches the target constructor. + // That cache is via a `StaticGet(real-constructor)` node, which we make + // here pass the verifier. + if (!node.target.hasGetter && node.target is! Constructor) { problem(node, "StaticGet of '${node.target}' without getter."); } if (node.target.isInstanceMember) { diff --git a/pkg/kernel/lib/visitor.dart b/pkg/kernel/lib/visitor.dart index 27f8e6a4920..7287c6992b8 100644 --- a/pkg/kernel/lib/visitor.dart +++ b/pkg/kernel/lib/visitor.dart @@ -47,6 +47,7 @@ abstract class ExpressionVisitor { R visitMapLiteral(MapLiteral node) => defaultExpression(node); R visitAwaitExpression(AwaitExpression node) => defaultExpression(node); R visitFunctionExpression(FunctionExpression node) => defaultExpression(node); + R visitConstantExpression(ConstantExpression node) => defaultExpression(node); R visitStringLiteral(StringLiteral node) => defaultBasicLiteral(node); R visitIntLiteral(IntLiteral node) => defaultBasicLiteral(node); R visitDoubleLiteral(DoubleLiteral node) => defaultBasicLiteral(node); @@ -167,6 +168,7 @@ class TreeVisitor R visitMapLiteral(MapLiteral node) => defaultExpression(node); R visitAwaitExpression(AwaitExpression node) => defaultExpression(node); R visitFunctionExpression(FunctionExpression node) => defaultExpression(node); + R visitConstantExpression(ConstantExpression node) => defaultExpression(node); R visitStringLiteral(StringLiteral node) => defaultBasicLiteral(node); R visitIntLiteral(IntLiteral node) => defaultBasicLiteral(node); R visitDoubleLiteral(DoubleLiteral node) => defaultBasicLiteral(node); @@ -258,6 +260,21 @@ class DartTypeVisitor { R visitTypedefType(TypedefType node) => defaultDartType(node); } +class ConstantVisitor { + R defaultConstant(Constant node) => null; + + R visitNullConstant(NullConstant node) => defaultConstant(node); + R visitBoolConstant(BoolConstant node) => defaultConstant(node); + R visitIntConstant(IntConstant node) => defaultConstant(node); + R visitDoubleConstant(DoubleConstant node) => defaultConstant(node); + R visitStringConstant(StringConstant node) => defaultConstant(node); + R visitMapConstant(MapConstant node) => defaultConstant(node); + R visitListConstant(ListConstant node) => defaultConstant(node); + R visitInstanceConstant(InstanceConstant node) => defaultConstant(node); + R visitTearOffConstant(TearOffConstant node) => defaultConstant(node); + R visitTypeLiteralConstant(TypeLiteralConstant node) => defaultConstant(node); +} + class MemberReferenceVisitor { const MemberReferenceVisitor(); @@ -269,7 +286,10 @@ class MemberReferenceVisitor { } class Visitor extends TreeVisitor - implements DartTypeVisitor, MemberReferenceVisitor { + implements + DartTypeVisitor, + ConstantVisitor, + MemberReferenceVisitor { const Visitor(); /// The catch-all case, except for references. @@ -288,10 +308,46 @@ class Visitor extends TreeVisitor R visitTypeParameterType(TypeParameterType node) => defaultDartType(node); R visitTypedefType(TypedefType node) => defaultDartType(node); + // Constants + R defaultConstant(Constant node) => defaultNode(node); + R visitNullConstant(NullConstant node) => defaultConstant(node); + R visitBoolConstant(BoolConstant node) => defaultConstant(node); + R visitIntConstant(IntConstant node) => defaultConstant(node); + R visitDoubleConstant(DoubleConstant node) => defaultConstant(node); + R visitStringConstant(StringConstant node) => defaultConstant(node); + R visitMapConstant(MapConstant node) => defaultConstant(node); + R visitListConstant(ListConstant node) => defaultConstant(node); + R visitInstanceConstant(InstanceConstant node) => defaultConstant(node); + R visitTearOffConstant(TearOffConstant node) => defaultConstant(node); + R visitTypeLiteralConstant(TypeLiteralConstant node) => defaultConstant(node); + // Class references R visitClassReference(Class node) => null; R visitTypedefReference(Typedef node) => null; + // Constant references + R defaultConstantReference(Constant node) => null; + R visitNullConstantReference(NullConstant node) => + defaultConstantReference(node); + R visitBoolConstantReference(BoolConstant node) => + defaultConstantReference(node); + R visitIntConstantReference(IntConstant node) => + defaultConstantReference(node); + R visitDoubleConstantReference(DoubleConstant node) => + defaultConstantReference(node); + R visitStringConstantReference(StringConstant node) => + defaultConstantReference(node); + R visitMapConstantReference(MapConstant node) => + defaultConstantReference(node); + R visitListConstantReference(ListConstant node) => + defaultConstantReference(node); + R visitInstanceConstantReference(InstanceConstant node) => + defaultConstantReference(node); + R visitTearOffConstantReference(TearOffConstant node) => + defaultConstantReference(node); + R visitTypeLiteralConstantReference(TypeLiteralConstant node) => + defaultConstantReference(node); + // Member references R defaultMemberReference(Member node) => null; R visitFieldReference(Field node) => defaultMemberReference(node); @@ -344,6 +400,8 @@ class Transformer extends TreeVisitor { /// By default, recursion stops at this point. DartType visitDartType(DartType node) => node; + Constant visitConstant(Constant node) => node; + Supertype visitSupertype(Supertype node) => node; TreeNode defaultTreeNode(TreeNode node) { @@ -398,6 +456,8 @@ abstract class ExpressionVisitor1 { R visitTypeLiteral(TypeLiteral node, T arg) => defaultExpression(node, arg); R visitThisExpression(ThisExpression node, T arg) => defaultExpression(node, arg); + R visitConstantExpression(ConstantExpression node, arg) => + defaultExpression(node, arg); R visitRethrow(Rethrow node, T arg) => defaultExpression(node, arg); R visitThrow(Throw node, T arg) => defaultExpression(node, arg); R visitListLiteral(ListLiteral node, T arg) => defaultExpression(node, arg); @@ -406,9 +466,9 @@ abstract class ExpressionVisitor1 { defaultExpression(node, arg); R visitFunctionExpression(FunctionExpression node, T arg) => defaultExpression(node, arg); + R visitIntLiteral(IntLiteral node, T arg) => defaultBasicLiteral(node, arg); R visitStringLiteral(StringLiteral node, T arg) => defaultBasicLiteral(node, arg); - R visitIntLiteral(IntLiteral node, T arg) => defaultBasicLiteral(node, arg); R visitDoubleLiteral(DoubleLiteral node, T arg) => defaultBasicLiteral(node, arg); R visitBoolLiteral(BoolLiteral node, T arg) => defaultBasicLiteral(node, arg); diff --git a/pkg/kernel/lib/vm/constants_native_effects.dart b/pkg/kernel/lib/vm/constants_native_effects.dart new file mode 100644 index 00000000000..b49184a2798 --- /dev/null +++ b/pkg/kernel/lib/vm/constants_native_effects.dart @@ -0,0 +1,84 @@ +// Copyright (c) 2017, 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. + +library vm.constants_native_effects; + +import '../ast.dart'; +import '../transformations/constants.dart'; +import '../core_types.dart'; + +class VmConstantsBackend implements ConstantsBackend { + final Class immutableMapClass; + + VmConstantsBackend._(this.immutableMapClass); + + factory VmConstantsBackend(CoreTypes coreTypes) { + final Library coreLibrary = coreTypes.coreLibrary; + final Class immutableMapClass = coreLibrary.classes + .firstWhere((Class klass) => klass.name == '_ImmutableMap'); + assert(immutableMapClass != null); + return new VmConstantsBackend._(immutableMapClass); + } + + Constant buildConstantForNative( + String nativeName, + List typeArguments, + List positionalArguments, + Map namedArguments) { + switch (nativeName) { + case 'Bool_fromEnvironment': + final String name = (positionalArguments[0] as StringConstant).value; + final Constant constant = namedArguments['defaultValue']; + final bool defaultValue = + constant is BoolConstant ? constant.value : false; + return new BoolConstant( + new bool.fromEnvironment(name, defaultValue: defaultValue)); + case 'Integer_fromEnvironment': + final String name = (positionalArguments[0] as StringConstant).value; + final Constant constant = namedArguments['defaultValue']; + final int defaultValue = + constant is IntConstant ? constant.value : null; + final int value = + new int.fromEnvironment(name, defaultValue: defaultValue); + return value != null ? new IntConstant(value) : new NullConstant(); + case 'String_fromEnvironment': + final String name = (positionalArguments[0] as StringConstant).value; + final Constant constant = namedArguments['defaultValue']; + final String defaultValue = + constant is StringConstant ? constant.value : null; + final String value = + new String.fromEnvironment(name, defaultValue: defaultValue); + return value == null ? new NullConstant() : new StringConstant(value); + } + throw 'No native effect registered for constant evaluation: $nativeName'; + } + + Constant lowerMapConstant(MapConstant constant) { + // The _ImmutableMap class is implemented via one field pointing to a list + // of key/value pairs -- see runtime/lib/immutable_map.dart! + final List kvListPairs = + new List(2 * constant.entries.length); + for (int i = 0; i < constant.entries.length; i++) { + final ConstantMapEntry entry = constant.entries[i]; + kvListPairs[2 * i] = entry.key; + kvListPairs[2 * i + 1] = entry.value; + } + // Strong mode is a bit fishy here, since we merge the key and the value + // type by putting both into the same list! + final kvListConstant = new ListConstant(const DynamicType(), kvListPairs); + assert(immutableMapClass.fields.length == 1); + final Field kvPairListField = immutableMapClass.fields[0]; + return new InstanceConstant(immutableMapClass.reference, [ + constant.keyType, + constant.valueType, + ], { + kvPairListField.reference: kvListConstant, + }); + } + + Constant lowerListConstant(ListConstant constant) { + // Currently we let vipunen deal with the [ListConstant]s. + return constant; + } +} diff --git a/pkg/pkg.status b/pkg/pkg.status index 3f1c7d02791..4c454cfe5e4 100644 --- a/pkg/pkg.status +++ b/pkg/pkg.status @@ -50,6 +50,7 @@ front_end/test/fasta/strong_test: Pass, Slow front_end/test/fasta/outline_test: Pass, Slow front_end/test/fasta/ast_builder_test: Pass, Slow front_end/tool/incremental_perf_test: Slow, Pass +front_end/test/whole_program_test: Slow, Pass # These are not tests but input for tests. kernel/testcases/*: Skip @@ -61,6 +62,9 @@ kernel/test/closures_test: Slow, Pass front_end/tool/_fasta/compile_platform_test: Fail +[ $use_sdk || $mode != release || $compiler != none || $runtime != vm || $arch != x64 ] +front_end/test/whole_program_test: SkipByDesign + [ $compiler != dart2analyzer ] analyzer/test/src/summary/summarize_fasta_test: RuntimeError, Slow diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc index 9e4e2dea4c2..07e00a1715a 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc @@ -1396,6 +1396,10 @@ void StreamingScopeBuilder::VisitExpression() { VisitDartType(); // read function type of the closure. builder_->SkipListOfDartTypes(); // read type arguments. return; + case kConstantExpression: { + builder_->SkipConstantReference(); + return; + } default: H.ReportError("Unsupported tag at this point: %d.", tag); UNREACHABLE(); @@ -2603,6 +2607,9 @@ Instance& StreamingConstantEvaluator::EvaluateExpression(intptr_t offset, case kNullLiteral: EvaluateNullLiteral(); break; + case kConstantExpression: + EvaluateConstantExpression(); + break; default: H.ReportError(script_, TokenPosition::kNoSource, "Not a constant expression."); @@ -3194,6 +3201,10 @@ void StreamingConstantEvaluator::EvaluateNullLiteral() { result_ = Instance::null(); } +void StreamingConstantEvaluator::EvaluateConstantExpression() { + result_ ^= H.constants().At(builder_->ReadUInt()); +} + // This depends on being about to read the list of positionals on arguments. const Object& StreamingConstantEvaluator::RunFunction( const Function& function, @@ -4398,6 +4409,8 @@ Fragment StreamingFlowGraphBuilder::BuildExpression(TokenPosition* position) { return BuildVectorCopy(position); case kClosureCreation: return BuildClosureCreation(position); + case kConstantExpression: + return BuildConstantExpression(position); default: H.ReportError("Unsupported tag at this point: %d.", tag); UNREACHABLE(); @@ -4568,6 +4581,10 @@ void StreamingFlowGraphBuilder::SkipStringReference() { ReadUInt(); } +void StreamingFlowGraphBuilder::SkipConstantReference() { + ReadUInt(); +} + void StreamingFlowGraphBuilder::SkipCanonicalNameReference() { ReadUInt(); } @@ -4934,6 +4951,9 @@ void StreamingFlowGraphBuilder::SkipExpression() { return; case kNullLiteral: return; + case kConstantExpression: + SkipConstantReference(); + return; default: H.ReportError("Unsupported tag at this point: %d.", tag); UNREACHABLE(); @@ -7469,6 +7489,13 @@ Fragment StreamingFlowGraphBuilder::BuildClosureCreation( return instructions; } +Fragment StreamingFlowGraphBuilder::BuildConstantExpression( + TokenPosition* position) { + if (position != NULL) *position = TokenPosition::kNoSource; + const intptr_t constant_index = ReadUInt(); + return Constant(Object::ZoneHandle(Z, H.constants().At(constant_index))); +} + Fragment StreamingFlowGraphBuilder::BuildInvalidStatement() { H.ReportError("Invalid statements not implemented yet!"); return Fragment(); @@ -8972,6 +8999,149 @@ void StreamingFlowGraphBuilder::EnsureMetadataIsScanned() { } } +const Array& ConstantHelper::ReadConstantTable() { + const intptr_t number_of_constants = builder_.ReadUInt(); + if (number_of_constants == 0) { + return Array::Handle(Z, Array::null()); + } + + const Library& corelib = Library::Handle(Z, Library::CoreLibrary()); + const Class& list_class = + Class::Handle(Z, corelib.LookupClassAllowPrivate(Symbols::_List())); + + // Eagerly finalize _ImmutableList (instead of doing it on every list + // constant). + temp_class_ = I->class_table()->At(kImmutableArrayCid); + temp_object_ = temp_class_.EnsureIsFinalized(H.thread()); + ASSERT(temp_object_.IsNull()); + + const Array& constants = + Array::Handle(Z, Array::New(number_of_constants, Heap::kOld)); + for (intptr_t i = 0; i < number_of_constants; ++i) { + const intptr_t constant_tag = builder_.ReadByte(); + switch (constant_tag) { + case kNullConstant: + temp_instance_ = Instance::null(); + break; + case kBoolConstant: + temp_instance_ = builder_.ReadByte() == 1 ? Object::bool_true().raw() + : Object::bool_false().raw(); + break; + case kIntConstant: { + temp_instance_ = const_evaluator_ + .EvaluateExpression(builder_.ReaderOffset(), + false /* reset position */) + .raw(); + break; + } + case kDoubleConstant: { + temp_instance_ = Double::New( + H.DartString(builder_.ReadStringReference()), Heap::kOld); + temp_instance_ = H.Canonicalize(temp_instance_); + break; + } + case kStringConstant: { + temp_instance_ = + H.Canonicalize(H.DartString(builder_.ReadStringReference())); + break; + } + case kListConstant: { + temp_type_arguments_ = TypeArguments::New(1, Heap::kOld); + const AbstractType& type = type_translator_.BuildType(); + temp_type_arguments_.SetTypeAt(0, type); + InstantiateTypeArguments(list_class, &temp_type_arguments_); + + const intptr_t length = builder_.ReadUInt(); + temp_array_ = ImmutableArray::New(length, Heap::kOld); + temp_array_.SetTypeArguments(temp_type_arguments_); + for (intptr_t j = 0; j < length; ++j) { + const intptr_t entry_index = builder_.ReadUInt(); + ASSERT(entry_index < i); // We have a DAG! + temp_object_ = constants.At(entry_index); + temp_array_.SetAt(j, temp_object_); + } + + temp_instance_ = H.Canonicalize(temp_array_); + break; + } + case kInstanceConstant: { + temp_class_ = + H.LookupClassByKernelClass(builder_.ReadCanonicalNameReference()); + temp_object_ = temp_class_.EnsureIsFinalized(H.thread()); + ASSERT(temp_object_.IsNull()); + + temp_instance_ = Instance::New(temp_class_, Heap::kOld); + + const intptr_t number_of_type_arguments = builder_.ReadUInt(); + if (number_of_type_arguments > 0) { + temp_type_arguments_ = + TypeArguments::New(number_of_type_arguments, Heap::kOld); + for (intptr_t j = 0; j < number_of_type_arguments; ++j) { + temp_type_arguments_.SetTypeAt(j, type_translator_.BuildType()); + } + InstantiateTypeArguments(list_class, &temp_type_arguments_); + temp_instance_.SetTypeArguments(temp_type_arguments_); + } + + const intptr_t number_of_fields = builder_.ReadUInt(); + for (intptr_t j = 0; j < number_of_fields; ++j) { + temp_field_ = + H.LookupFieldByKernelField(builder_.ReadCanonicalNameReference()); + const intptr_t entry_index = builder_.ReadUInt(); + ASSERT(entry_index < i); // We have a DAG! + temp_object_ = constants.At(entry_index); + temp_instance_.SetField(temp_field_, temp_object_); + } + + temp_instance_ = H.Canonicalize(temp_instance_); + break; + } + case kTearOffConstant: { + const NameIndex index = builder_.ReadCanonicalNameReference(); + NameIndex lib_index = index; + while (!H.IsLibrary(lib_index)) { + lib_index = H.CanonicalNameParent(lib_index); + } + ASSERT(H.IsLibrary(lib_index)); + if (lib_index == skip_vmservice_library_) { + temp_instance_ = Instance::null(); + break; + } + + temp_function_ = H.LookupStaticMethodByKernelProcedure(index); + temp_function_ = temp_function_.ImplicitClosureFunction(); + temp_instance_ = temp_function_.ImplicitStaticClosure(); + temp_instance_ = H.Canonicalize(temp_instance_); + break; + } + case kTypeLiteralConstant: { + temp_instance_ = type_translator_.BuildType().raw(); + break; + } + case kMapConstant: + // Note: This is already lowered to InstanceConstant/ListConstant. + UNREACHABLE(); + break; + default: + UNREACHABLE(); + } + constants.SetAt(i, temp_instance_); + } + return constants; +} + +void ConstantHelper::InstantiateTypeArguments(const Class& receiver_class, + TypeArguments* type_arguments) { + // We make a temporary [Type] object and use `ClassFinalizer::FinalizeType` to + // finalize the argument types. + // (This can for example make the [type_arguments] vector larger) + temp_type_ = + Type::New(receiver_class, *type_arguments, TokenPosition::kNoSource); + temp_type_ = ClassFinalizer::FinalizeType(*active_class_->klass, temp_type_, + ClassFinalizer::kCanonicalize); + *type_arguments = temp_type_.arguments(); +} + } // namespace kernel } // namespace dart diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h index a9bd6692a82..401a393af5f 100644 --- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h +++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h @@ -777,6 +777,7 @@ class StreamingConstantEvaluator { void EvaluateDoubleLiteral(); void EvaluateBoolLiteral(bool value); void EvaluateNullLiteral(); + void EvaluateConstantExpression(); void EvaluateGetStringLength(intptr_t expression_offset, TokenPosition position); @@ -948,6 +949,7 @@ class StreamingFlowGraphBuilder { const String& ReadNameAsFieldName(); void SkipFlags(); void SkipStringReference(); + void SkipConstantReference(); void SkipCanonicalNameReference(); void SkipDartType(); void SkipOptionalDartType(); @@ -1179,6 +1181,7 @@ class StreamingFlowGraphBuilder { Fragment BuildVectorSet(TokenPosition* position); Fragment BuildVectorCopy(TokenPosition* position); Fragment BuildClosureCreation(TokenPosition* position); + Fragment BuildConstantExpression(TokenPosition* position); Fragment BuildInvalidStatement(); Fragment BuildExpressionStatement(); @@ -1236,21 +1239,23 @@ class StreamingFlowGraphBuilder { DirectCallMetadataHelper direct_call_metadata_helper_; bool metadata_scanned_; + friend class ClassHelper; + friend class ConstantHelper; + friend class ConstructorHelper; + friend class DirectCallMetadataHelper; + friend class FieldHelper; + friend class FunctionNodeHelper; + friend class KernelLoader; + friend class KernelReader; + friend class LibraryDependencyHelper; + friend class LibraryHelper; + friend class MetadataHelper; + friend class ProcedureHelper; + friend class SimpleExpressionConverter; friend class StreamingConstantEvaluator; friend class StreamingDartTypeTranslator; friend class StreamingScopeBuilder; - friend class FunctionNodeHelper; friend class VariableDeclarationHelper; - friend class FieldHelper; - friend class ProcedureHelper; - friend class ClassHelper; - friend class LibraryHelper; - friend class LibraryDependencyHelper; - friend class MetadataHelper; - friend class DirectCallMetadataHelper; - friend class ConstructorHelper; - friend class SimpleExpressionConverter; - friend class KernelLoader; }; // A helper class that saves the current reader position, goes to another reader @@ -1304,6 +1309,60 @@ class AlternativeReadingScope { intptr_t saved_offset_; }; +// Helper class that reads a kernel Constant from binary. +class ConstantHelper { + public: + ConstantHelper(ActiveClass* active_class, + StreamingFlowGraphBuilder* builder, + StreamingDartTypeTranslator* type_translator, + TranslationHelper* translation_helper, + Zone* zone, + NameIndex skip_vmservice_library) + : skip_vmservice_library_(skip_vmservice_library), + active_class_(active_class), + builder_(*builder), + type_translator_(*type_translator), + const_evaluator_(&builder_), + translation_helper_(*translation_helper), + zone_(zone), + temp_type_(AbstractType::Handle(zone)), + temp_type_arguments_(TypeArguments::Handle(zone)), + temp_object_(Object::Handle(zone)), + temp_array_(Array::Handle(zone)), + temp_instance_(Instance::Handle(zone)), + temp_field_(Field::Handle(zone)), + temp_class_(Class::Handle(zone)), + temp_function_(Function::Handle(zone)), + temp_integer_(Integer::Handle(zone)) {} + + // Reads the constant table from the binary. + // + // This method assumes the Reader is positioned already at the constant table + // and an active class scope is setup. + const Array& ReadConstantTable(); + + private: + void InstantiateTypeArguments(const Class& receiver_class, + TypeArguments* type_arguments); + + NameIndex skip_vmservice_library_; + ActiveClass* active_class_; + StreamingFlowGraphBuilder& builder_; + StreamingDartTypeTranslator& type_translator_; + StreamingConstantEvaluator const_evaluator_; + TranslationHelper translation_helper_; + Zone* zone_; + AbstractType& temp_type_; + TypeArguments& temp_type_arguments_; + Object& temp_object_; + Array& temp_array_; + Instance& temp_instance_; + Field& temp_field_; + Class& temp_class_; + Function& temp_function_; + Integer& temp_integer_; +}; + } // namespace kernel } // namespace dart diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 73d4e51a338..79f97f40204 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -153,7 +153,8 @@ TranslationHelper::TranslationHelper(Thread* thread) string_data_(TypedData::Handle(Z)), canonical_names_(TypedData::Handle(Z)), metadata_payloads_(TypedData::Handle(Z)), - metadata_mappings_(TypedData::Handle(Z)) {} + metadata_mappings_(TypedData::Handle(Z)), + constants_(Array::Handle(Z)) {} void TranslationHelper::InitFromScript(const Script& script) { const KernelProgramInfo& info = @@ -175,6 +176,7 @@ void TranslationHelper::InitFromKernelProgramInfo( SetCanonicalNames(TypedData::Handle(Z, info.canonical_names())); SetMetadataPayloads(TypedData::Handle(Z, info.metadata_payloads())); SetMetadataMappings(TypedData::Handle(Z, info.metadata_mappings())); + SetConstants(Array::Handle(Z, info.constants())); } void TranslationHelper::SetStringOffsets(const TypedData& string_offsets) { @@ -204,6 +206,11 @@ void TranslationHelper::SetMetadataMappings( metadata_mappings_ = metadata_mappings.raw(); } +void TranslationHelper::SetConstants(const Array& constants) { + ASSERT(constants_.IsNull()); + constants_ = constants.raw(); +} + intptr_t TranslationHelper::StringOffset(StringIndex index) const { return string_offsets_.GetUint32(index << 2); } @@ -766,7 +773,8 @@ FlowGraphBuilder::FlowGraphBuilder( next_used_try_index_(0), catch_block_(NULL), streaming_flow_graph_builder_(NULL) { - Script& script = Script::Handle(Z, parsed_function->function().script()); + const Script& script = + Script::Handle(Z, parsed_function->function().script()); H.InitFromScript(script); } @@ -1788,6 +1796,19 @@ void FlowGraphBuilder::InlineBailout(const char* reason) { FlowGraph* FlowGraphBuilder::BuildGraph() { const Function& function = parsed_function_->function(); + +#ifdef DEBUG + // If we attached the native name to the function after it's creation (namely + // after reading the constant table from the kernel blob), we must have done + // so before building flow graph for the functions (since FGB depends needs + // the native name to be there). + const Script& script = Script::Handle(Z, function.script()); + const KernelProgramInfo& info = + KernelProgramInfo::Handle(script.kernel_program_info()); + ASSERT(info.IsNull() || + info.potential_natives() == GrowableObjectArray::null()); +#endif + StreamingFlowGraphBuilder streaming_flow_graph_builder( this, TypedData::Handle(Z, function.KernelData()), function.KernelDataProgramOffset()); diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 8b3a760cc35..c5be90f8739 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -321,6 +321,9 @@ class TranslationHelper { const TypedData& metadata_mappings() { return metadata_mappings_; } void SetMetadataMappings(const TypedData& metadata_mappings); + const Array& constants() { return constants_; } + void SetConstants(const Array& constants); + intptr_t StringOffset(StringIndex index) const; intptr_t StringSize(StringIndex index) const; @@ -443,6 +446,7 @@ class TranslationHelper { TypedData& canonical_names_; TypedData& metadata_payloads_; TypedData& metadata_mappings_; + Array& constants_; }; struct FunctionScope { diff --git a/runtime/vm/kernel.h b/runtime/vm/kernel.h index 4224eb26e97..6bb2b3585e8 100644 --- a/runtime/vm/kernel.h +++ b/runtime/vm/kernel.h @@ -78,6 +78,7 @@ class Program { intptr_t source_table_offset() const { return source_table_offset_; } intptr_t string_table_offset() const { return string_table_offset_; } intptr_t name_table_offset() const { return name_table_offset_; } + intptr_t constant_table_offset() { return constant_table_offset_; } const uint8_t* kernel_data() { return kernel_data_; } intptr_t kernel_data_size() { return kernel_data_size_; } intptr_t library_count() { return library_count_; } @@ -97,6 +98,9 @@ class Program { // The offset from the start of the binary to the start of the source table. intptr_t source_table_offset_; + // The offset from the start of the binary to the start of the constant table. + intptr_t constant_table_offset_; + // The offset from the start of the binary to the canonical name table. intptr_t name_table_offset_; diff --git a/runtime/vm/kernel_binary.cc b/runtime/vm/kernel_binary.cc index a63ad5d4587..d4fcd65f464 100644 --- a/runtime/vm/kernel_binary.cc +++ b/runtime/vm/kernel_binary.cc @@ -51,9 +51,13 @@ Program* Program::ReadFrom(Reader* reader, bool take_buffer_ownership) { reader->size_, LibraryCountFieldCountFromEnd, 1, 0); program->source_table_offset_ = reader->ReadFromIndexNoReset( reader->size_, - LibraryCountFieldCountFromEnd + 1 + program->library_count_ + 1, 4, 0); + LibraryCountFieldCountFromEnd + 1 + program->library_count_ + 1 + + SourceTableFieldCountFromFirstLibraryOffset, + 1, 0); program->name_table_offset_ = reader->ReadUInt32(); program->string_table_offset_ = reader->ReadUInt32(); + program->constant_table_offset_ = reader->ReadUInt32(); + program->main_method_reference_ = NameIndex(reader->ReadUInt32() - 1); return program; diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h index 186939d2a01..16b2fd4e007 100644 --- a/runtime/vm/kernel_binary.h +++ b/runtime/vm/kernel_binary.h @@ -21,6 +21,7 @@ namespace kernel { static const uint32_t kMagicProgramFile = 0x90ABCDEFu; static const uint32_t kBinaryFormatVersion = 1; +// Keep in sync with package:kernel/lib/binary/tag.dart enum Tag { kNothing = 0, kSomething = 1, @@ -125,6 +126,8 @@ enum Tag { kClosureCreation = 106, + kConstantExpression = 107, + kSpecializedTagHighBit = 0x80, // 10000000 kSpecializedTagMask = 0xF8, // 11111000 kSpecializedPayloadMask = 0x7, // 00000111 @@ -134,9 +137,23 @@ enum Tag { kSpecialIntLiteral = 144, }; +// Keep in sync with package:kernel/lib/binary/tag.dart +enum ConstantTag { + kNullConstant = 0, + kBoolConstant = 1, + kIntConstant = 2, + kDoubleConstant = 3, + kStringConstant = 4, + kMapConstant = 5, + kListConstant = 6, + kInstanceConstant = 7, + kTearOffConstant = 8, + kTypeLiteralConstant = 9, +}; + static const int SpecializedIntLiteralBias = 3; static const int LibraryCountFieldCountFromEnd = 1; -static const int SourceTableFieldCountFromFirstLibraryOffset = 3; +static const int SourceTableFieldCountFromFirstLibraryOffset = 4; static const int HeaderSize = 8; // 'magic', 'formatVersion'. static const int MetadataPayloadOffset = HeaderSize; // Right after header. diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index caf69470608..b21f5940c94 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -175,7 +175,10 @@ KernelLoader::KernelLoader(Program* program) zone_, program_->kernel_data(), program_->kernel_data_size(), - 0) { + 0), + external_name_class_(Class::Handle(Z)), + external_name_field_(Field::Handle(Z)), + potential_natives_(GrowableObjectArray::Handle(Z)) { if (!program->is_single_program()) { FATAL( "Trying to load a concatenated dill file at a time where that is " @@ -344,7 +347,10 @@ KernelLoader::KernelLoader(const Script& script, kernel_program_info_( KernelProgramInfo::ZoneHandle(zone_, script.kernel_program_info())), translation_helper_(this, thread_), - builder_(&translation_helper_, script.raw(), zone_, kernel_data, 0) { + builder_(&translation_helper_, script.raw(), zone_, kernel_data, 0), + external_name_class_(Class::Handle(Z)), + external_name_field_(Field::Handle(Z)), + potential_natives_(GrowableObjectArray::Handle(Z)) { T.active_class_ = &active_class_; T.finalize_ = false; @@ -355,7 +361,84 @@ KernelLoader::KernelLoader(const Script& script, H.InitFromKernelProgramInfo(kernel_program_info_); } +const Array& KernelLoader::ReadConstantTable() { + // We use the very first library's toplevel class as an owner for an + // [ActiveClassScope] + // + // Though since constants cannot refer to types containing type parameter + // references, the only purpose of the class is to serve as an owner for + // signature functions (which get created for function types). + const dart::Library& owner_library = LookupLibrary(library_canonical_name(0)); + const dart::Class& toplevel_class = + Class::Handle(Z, owner_library.toplevel_class()); + ActiveClassScope active_class_scope(&active_class_, &toplevel_class); + + builder_.SetOffset(program_->constant_table_offset()); + StreamingDartTypeTranslator type_translator_(&builder_, true /* finalize */); + type_translator_.active_class_ = &active_class_; + + ConstantHelper helper(&active_class_, &builder_, &type_translator_, + &translation_helper_, Z, skip_vmservice_library_); + return helper.ReadConstantTable(); +} + +void KernelLoader::AnnotateNativeProcedures(const Array& constant_table) { + potential_natives_ = kernel_program_info_.potential_natives(); + const intptr_t length = + !potential_natives_.IsNull() ? potential_natives_.Length() : 0; + if (length > 0) { + // Obtain `dart:_internal::ExternalName.name`. + EnsureExternalClassIsLookedUp(); + Instance& constant = Instance::Handle(Z); + String& native_name = String::Handle(Z); + + // Start scanning all candidates in [potential_natives] for the annotation + // constant. If the annotation is found, flag the [Function] as native and + // attach the native name to it. + Function& function = Function::Handle(Z); + for (intptr_t i = 0; i < length; ++i) { + function ^= potential_natives_.At(i); + builder_.SetOffset(function.KernelDataProgramOffset() + + function.kernel_offset()); + { + ProcedureHelper procedure_helper(&builder_); + procedure_helper.ReadUntilExcluding(ProcedureHelper::kAnnotations); + } + + const intptr_t annotation_count = builder_.ReadListLength(); + for (intptr_t j = 0; j < annotation_count; ++j) { + const intptr_t tag = builder_.PeekTag(); + if (tag == kConstantExpression) { + builder_.ReadByte(); // Skip the tag. + + // We have a candiate. Let's look if it's an instance of the + // ExternalName class. + const intptr_t constant_table_index = builder_.ReadUInt(); + constant ^= constant_table.At(constant_table_index); + if (constant.clazz() == external_name_class_.raw()) { + // We found the annotation, let's flag the function as native and + // set the native name! + native_name ^= constant.GetField(external_name_field_); + function.set_is_native(true); + function.set_native_name(native_name); + break; + } + } else { + builder_.SkipExpression(); + } + } + } + + // Clear out the list of [Function] objects which might need their native + // name to be set after reading the constant table from the kernel blob. + potential_natives_ = GrowableObjectArray::null(); + kernel_program_info_.set_potential_natives(potential_natives_); + } +} + Object& KernelLoader::LoadProgram(bool process_pending_classes) { + ASSERT(kernel_program_info_.constants() == Array::null()); + if (!program_->is_single_program()) { FATAL( "Trying to load a concatenated dill file at a time where that is " @@ -364,32 +447,46 @@ Object& KernelLoader::LoadProgram(bool process_pending_classes) { LongJumpScope jump; if (setjmp(*jump.Set()) == 0) { - intptr_t length = program_->library_count(); + const intptr_t length = program_->library_count(); for (intptr_t i = 0; i < length; i++) { LoadLibrary(i); } - if (process_pending_classes && ClassFinalizer::ProcessPendingClasses()) { - // If 'main' is not found return a null library, this is the case - // when bootstrapping is in progress. - NameIndex main = program_->main_method(); - if (main == -1) { - return Library::Handle(Z); + if (process_pending_classes) { + if (!ClassFinalizer::ProcessPendingClasses()) { + // Class finalization failed -> sticky error would be set. + Error& error = Error::Handle(Z); + error = H.thread()->sticky_error(); + H.thread()->clear_sticky_error(); + return error; } - - NameIndex main_library = H.EnclosingName(main); - Library& library = LookupLibrary(main_library); - return library; - } else if (!process_pending_classes) { - NameIndex main = program_->main_method(); - if (main == -1) { - return Library::Handle(Z); - } - - NameIndex main_library = H.EnclosingName(main); - Library& library = LookupLibrary(main_library); - return library; } + + // All classes were successfully loaded, so let's: + // a) load & canonicalize the constant table + const Array& constants = ReadConstantTable(); + + // b) set the native names for native functions which have been created + // so far (the rest will be directly set during LoadProcedure) + AnnotateNativeProcedures(constants); + ASSERT(kernel_program_info_.constants() == Array::null()); + + // c) update all scripts with the constants array + kernel_program_info_.set_constants(constants); + + NameIndex main = program_->main_method(); + if (main == -1) { + return Library::Handle(Z); + } + + NameIndex main_library = H.EnclosingName(main); + Library& library = LookupLibrary(main_library); + + // Sanity check that we can find the main entrypoint. + ASSERT(library.LookupObjectAllowPrivate(H.DartSymbol("main")) != + Object::null()); + + return library; } // Either class finalization failed or we caught a compile error. @@ -495,11 +592,14 @@ void KernelLoader::LoadLibrary(intptr_t index) { if (H.StringEquals(lib_name_index, kVMServiceIOLibraryUri)) { // We are not the service isolate and we are not generating an AOT // snapshot so we skip loading 'dart:vmservice_io'. + skip_vmservice_library_ = library_helper.canonical_name_; + ASSERT(H.IsLibrary(skip_vmservice_library_)); return; } } Library& library = LookupLibrary(library_helper.canonical_name_); + // The Kernel library is external implies that it is already loaded. ASSERT(!library_helper.IsExternal() || library.Loaded()); if (library.Loaded()) return; @@ -964,58 +1064,98 @@ void KernelLoader::LoadProcedure(const Library& library, bool is_external = procedure_helper.IsExternal(); String* native_name = NULL; intptr_t annotation_count; + bool is_potential_native = false; if (is_external) { // Maybe it has a native implementation, which is not external as far as // the VM is concerned because it does have an implementation. Check for // an ExternalName annotation and extract the string from it. annotation_count = builder_.ReadListLength(); // read list length. for (int i = 0; i < annotation_count; ++i) { - if (builder_.PeekTag() != kConstructorInvocation && - builder_.PeekTag() != kConstConstructorInvocation) { + const intptr_t tag = builder_.PeekTag(); + if (tag == kConstructorInvocation || tag == kConstConstructorInvocation) { + builder_.ReadTag(); + builder_.ReadPosition(); + NameIndex annotation_class = H.EnclosingName( + builder_.ReadCanonicalNameReference()); // read target reference, + ASSERT(H.IsClass(annotation_class)); + StringIndex class_name_index = H.CanonicalNameString(annotation_class); + // Just compare by name, do not generate the annotation class. + if (!H.StringEquals(class_name_index, "ExternalName")) { + builder_.SkipArguments(); + continue; + } + ASSERT(H.IsLibrary(H.CanonicalNameParent(annotation_class))); + StringIndex library_name_index = + H.CanonicalNameString(H.CanonicalNameParent(annotation_class)); + if (!H.StringEquals(library_name_index, "dart:_internal")) { + builder_.SkipArguments(); + continue; + } + + is_external = false; + // Read arguments: + intptr_t total_arguments = builder_.ReadUInt(); // read argument count. + builder_.SkipListOfDartTypes(); // read list of types. + intptr_t positional_arguments = builder_.ReadListLength(); + ASSERT(total_arguments == 1 && positional_arguments == 1); + + Tag tag = builder_.ReadTag(); + ASSERT(tag == kStringLiteral); + native_name = &H.DartSymbol( + builder_.ReadStringReference()); // read index into string table. + + // List of named. + intptr_t list_length = builder_.ReadListLength(); // read list length. + ASSERT(list_length == 0); + + // Skip remaining annotations + for (++i; i < annotation_count; ++i) { + builder_.SkipExpression(); // read ith annotation. + } + break; + } else if (tag == kConstantExpression) { + if (kernel_program_info_.constants() == Array::null()) { + // We can only read in the constant table once all classes have been + // finalized (otherwise we can't create instances of the classes!). + // + // We therefore delay the scanning for `ExternalName {name: ... }` + // constants in the annotation list to later. + is_potential_native = true; + builder_.SkipExpression(); + } else { + builder_.ReadByte(); // Skip the tag. + + // Obtain `dart:_internal::ExternalName.name`. + EnsureExternalClassIsLookedUp(); + + const Array& constant_table = + Array::Handle(kernel_program_info_.constants()); + + // We have a candiate. Let's look if it's an instance of the + // ExternalName class. + const intptr_t constant_table_index = builder_.ReadUInt(); + const Object& constant = + Object::Handle(constant_table.At(constant_table_index)); + if (constant.clazz() == external_name_class_.raw()) { + const Instance& instance = + Instance::Handle(Instance::RawCast(constant.raw())); + + // We found the annotation, let's flag the function as native and + // set the native name! + native_name = &String::Handle( + String::RawCast(instance.GetField(external_name_field_))); + + // Skip remaining annotations + for (++i; i < annotation_count; ++i) { + builder_.SkipExpression(); // read ith annotation. + } + break; + } + } + } else { builder_.SkipExpression(); continue; } - builder_.ReadTag(); - builder_.ReadPosition(); - NameIndex annotation_class = H.EnclosingName( - builder_.ReadCanonicalNameReference()); // read target reference, - ASSERT(H.IsClass(annotation_class)); - StringIndex class_name_index = H.CanonicalNameString(annotation_class); - // Just compare by name, do not generate the annotation class. - if (!H.StringEquals(class_name_index, "ExternalName")) { - builder_.SkipArguments(); - continue; - } - ASSERT(H.IsLibrary(H.CanonicalNameParent(annotation_class))); - StringIndex library_name_index = - H.CanonicalNameString(H.CanonicalNameParent(annotation_class)); - if (!H.StringEquals(library_name_index, "dart:_internal")) { - builder_.SkipArguments(); - continue; - } - - is_external = false; - // Read arguments: - intptr_t total_arguments = builder_.ReadUInt(); // read argument count. - builder_.SkipListOfDartTypes(); // read list of types. - intptr_t positional_arguments = builder_.ReadListLength(); - ASSERT(total_arguments == 1 && positional_arguments == 1); - - Tag tag = builder_.ReadTag(); - ASSERT(tag == kStringLiteral); - native_name = &H.DartSymbol( - builder_.ReadStringReference()); // read index into string table. - - // List of named. - intptr_t list_length = builder_.ReadListLength(); // read list length. - ASSERT(list_length == 0); - - // Skip remaining annotations - for (++i; i < annotation_count; ++i) { - builder_.SkipExpression(); // read ith annotation. - } - - break; } procedure_helper.SetJustRead(ProcedureHelper::kAnnotations); } else { @@ -1066,6 +1206,10 @@ void KernelLoader::LoadProcedure(const Library& library, if (native_name != NULL) { function.set_native_name(*native_name); } + if (is_potential_native) { + EnsurePotentialNatives(); + potential_natives_.Add(function); + } function_node_helper.ReadUntilExcluding(FunctionNodeHelper::kTypeParameters); builder_.SetupFunctionParameters(&active_class_, owner, function, is_method, diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h index a0d383f77fa..7758a5812ee 100644 --- a/runtime/vm/kernel_loader.h +++ b/runtime/vm/kernel_loader.h @@ -140,6 +140,13 @@ class KernelLoader { static void FinishLoading(const Class& klass); + const Array& ReadConstantTable(); + void AnnotateNativeProcedures(const Array& constant_table); + + const String& DartSymbol(StringIndex index) { + return translation_helper_.DartSymbol(index); + } + const String& LibraryUri(intptr_t library_index) { return translation_helper_.DartSymbol( translation_helper_.CanonicalNameString( @@ -221,6 +228,28 @@ class KernelLoader { RawFunction::Kind GetFunctionType(ProcedureHelper::Kind procedure_kind); + void EnsureExternalClassIsLookedUp() { + if (external_name_class_.IsNull()) { + ASSERT(external_name_field_.IsNull()); + const Library& internal_lib = + Library::Handle(zone_, dart::Library::InternalLibrary()); + external_name_class_ = internal_lib.LookupClass(Symbols::ExternalName()); + external_name_field_ = external_name_class_.LookupField(Symbols::name()); + } else { + ASSERT(!external_name_field_.IsNull()); + } + } + + void EnsurePotentialNatives() { + potential_natives_ = kernel_program_info_.potential_natives(); + if (potential_natives_.IsNull()) { + // To avoid too many grows in this array, we'll set it's initial size to + // something close to the actual number of potential native functions. + potential_natives_ = GrowableObjectArray::New(100, Heap::kNew); + kernel_program_info_.set_potential_natives(potential_natives_); + } + } + Program* program_; Thread* thread_; @@ -236,11 +265,18 @@ class KernelLoader { // to their library's kernel data, have to be corrected. intptr_t correction_offset_; bool loading_native_wrappers_library_; + + NameIndex skip_vmservice_library_; + TypedData& library_kernel_data_; KernelProgramInfo& kernel_program_info_; BuildingTranslationHelper translation_helper_; StreamingFlowGraphBuilder builder_; + Class& external_name_class_; + Field& external_name_field_; + GrowableObjectArray& potential_natives_; + Mapping libraries_; Mapping classes_; diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index de397564ca4..f7c04386b3d 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -5539,14 +5539,20 @@ RawFunction* Function::implicit_closure_function() const { void Function::set_implicit_closure_function(const Function& value) const { ASSERT(!IsClosureFunction() && !IsSignatureFunction() && !IsConvertedClosureFunction()); + const Object& old_data = Object::Handle(raw_ptr()->data_); if (is_native()) { - const Object& obj = Object::Handle(raw_ptr()->data_); - ASSERT(obj.IsArray()); - ASSERT((Array::Cast(obj).At(1) == Object::null()) || value.IsNull()); - Array::Cast(obj).SetAt(1, value); + ASSERT(old_data.IsArray()); + ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull()); + Array::Cast(old_data).SetAt(1, value); } else { - ASSERT((raw_ptr()->data_ == Object::null()) || value.IsNull()); - set_data(value); + // Maybe this function will turn into a native later on :-/ + if (old_data.IsArray()) { + ASSERT((Array::Cast(old_data).At(1) == Object::null()) || value.IsNull()); + Array::Cast(old_data).SetAt(1, value); + } else { + ASSERT(old_data.IsNull() || value.IsNull()); + set_data(value); + } } } @@ -5827,11 +5833,26 @@ RawString* Function::native_name() const { } void Function::set_native_name(const String& value) const { + Zone* zone = Thread::Current()->zone(); ASSERT(is_native()); - ASSERT(raw_ptr()->data_ == Object::null()); - const Array& pair = Array::Handle(Array::New(2, Heap::kOld)); + + // Due to the fact that kernel needs to read in the constant table before the + // annotation data is available, we don't know at function creation time + // whether the function is a native or not. + // + // Reading the constant table can cause a static function to get an implicit + // closure function. + // + // We therefore handle both cases. + const Object& old_data = Object::Handle(zone, raw_ptr()->data_); + ASSERT(old_data.IsNull() || + (old_data.IsFunction() && + Function::Handle(zone, Function::RawCast(old_data.raw())) + .IsImplicitClosureFunction())); + + const Array& pair = Array::Handle(zone, Array::New(2, Heap::kOld)); pair.SetAt(0, value); - // pair[1] will be the implicit closure function if needed. + pair.SetAt(1, old_data); // will be the implicit closure function if needed. set_data(pair); } @@ -11921,6 +11942,15 @@ RawScript* KernelProgramInfo::ScriptAt(intptr_t index) const { return Script::RawCast(script); } +void KernelProgramInfo::set_constants(const Array& constants) const { + StorePointer(&raw_ptr()->constants_, constants.raw()); +} + +void KernelProgramInfo::set_potential_natives( + const GrowableObjectArray& candidates) const { + StorePointer(&raw_ptr()->potential_natives_, candidates.raw()); +} + RawError* Library::CompileAll() { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); diff --git a/runtime/vm/object.h b/runtime/vm/object.h index af48f413bf0..ed80d2ff302 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -3979,6 +3979,19 @@ class KernelProgramInfo : public Object { RawArray* scripts() const { return raw_ptr()->scripts_; } + RawArray* constants() const { return raw_ptr()->constants_; } + void set_constants(const Array& constants) const; + + // If we load a kernel blob with evaluated constants, then we delay setting + // the native names of [Function] objects until we've read the constant table + // (since native names are encoded as constants). + // + // This array will hold the functions which might need their native name set. + RawGrowableObjectArray* potential_natives() const { + return raw_ptr()->potential_natives_; + } + void set_potential_natives(const GrowableObjectArray& candidates) const; + RawScript* ScriptAt(intptr_t index) const; private: diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index b29901408b0..0e9af5fb520 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -1204,7 +1204,9 @@ class RawKernelProgramInfo : public RawObject { RawTypedData* metadata_payloads_; RawTypedData* metadata_mappings_; RawArray* scripts_; - VISIT_TO(RawObject*, scripts_); + RawArray* constants_; + RawGrowableObjectArray* potential_natives_; + VISIT_TO(RawObject*, potential_natives_); }; class RawCode : public RawObject { diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 16ffa3d2b82..d49aa759ea9 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -372,6 +372,7 @@ class ObjectPointerVisitor; V(DartIOLibName, "dart.io") \ V(DartVMProduct, "dart.vm.product") \ V(EvalSourceUri, "evaluate:source") \ + V(ExternalName, "ExternalName") \ V(_Random, "_Random") \ V(_state, "_state") \ V(_A, "_A") \ @@ -429,6 +430,7 @@ class ObjectPointerVisitor; V(DartLibrary, "dart.library.") \ V(DartLibraryMirrors, "dart.library.mirrors") \ V(_name, "_name") \ + V(name, "name") \ V(_classRangeCheck, "_classRangeCheck") \ V(_classRangeCheckNegative, "_classRangeCheckNegative") \ V(_classRangeAssert, "_classRangeAssert") \