// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library kernel.ast_to_binary; import '../ast.dart'; import '../import_table.dart'; import 'tag.dart'; import 'dart:convert'; import 'dart:typed_data'; import 'dart:collection'; /// Writes to a binary file. /// /// A [BinaryPrinter] can be used to write one file and must then be /// discarded. class BinaryPrinter extends Visitor { ImportTable _importTable; VariableIndexer _variableIndexer; LabelIndexer _labelIndexer; SwitchCaseIndexer _switchCaseIndexer; final TypeParameterIndexer _typeParameterIndexer = new TypeParameterIndexer(); final GlobalIndexer _globalIndexer; final StringIndexer _stringIndexer = new StringIndexer(); final StringIndexer _sourceUriIndexer = new StringIndexer(); final BufferedSink _sink; /// Create a printer that writes to the given [sink]. /// /// The BinaryPrinter will use its own buffer, so the [sink] does not need /// one. /// /// If multiple binaries are to be written based on the same IR, a shared /// [globalIndexer] may be passed in to avoid rebuilding the same indices /// in every printer. BinaryPrinter(Sink> sink, {GlobalIndexer globalIndexer}) : _sink = new BufferedSink(sink), _globalIndexer = globalIndexer ?? new GlobalIndexer(); void _flush() { _sink.flushAndDestroy(); } void writeByte(int byte) { _sink.addByte(byte); } void writeBytes(List bytes) { _sink.addBytes(bytes); } void writeUInt30(int value) { assert(value >= 0 && value >> 30 == 0); if (value < 0x80) { writeByte(value); } else if (value < 0x4000) { writeByte((value >> 8) | 0x80); writeByte(value & 0xFF); } else { writeByte((value >> 24) | 0xC0); writeByte((value >> 16) & 0xFF); writeByte((value >> 8) & 0xFF); writeByte(value & 0xFF); } } void writeMagicWord(int value) { writeByte((value >> 24) & 0xFF); writeByte((value >> 16) & 0xFF); writeByte((value >> 8) & 0xFF); writeByte(value & 0xFF); } void writeStringTableEntry(String string) { List utf8Bytes = const Utf8Encoder().convert(string); writeUInt30(utf8Bytes.length); writeBytes(utf8Bytes); } void writeStringTable(StringIndexer indexer) { writeUInt30(indexer.numberOfStrings); for (var entry in indexer.entries) { writeStringTableEntry(entry.value); } } void writeStringReference(String string) { writeUInt30(_stringIndexer[string]); } void writeUriReference(String string) { int index = _sourceUriIndexer[string]; if (index == null) { // Assume file was loaded without linking. Bail out to empty string. index = _sourceUriIndexer[""]; } writeUInt30(index); } void writeList(List items, writeItem(x)) { writeUInt30(items.length); items.forEach(writeItem); } void writeNodeList(List nodes) { writeList(nodes, writeNode); } void writeNode(Node node) { node.accept(this); } void writeOptionalNode(Node node) { if (node == null) { writeByte(Tag.Nothing); } else { writeByte(Tag.Something); writeNode(node); } } void writeOptionalInferredValue(InferredValue node) { if (node == null) { writeByte(Tag.Nothing); } else { writeByte(Tag.Something); writeClassReference(node.baseClass, allowNull: true); writeByte(node.baseClassKind.index); writeByte(node.valueBits); } } void writeProgramFile(Program program) { writeMagicWord(Tag.ProgramFile); _importTable = new ProgramImportTable(program); _stringIndexer.build(program); writeStringTable(_stringIndexer); writeUriToSource(program); writeList(program.libraries, writeNode); writeMemberReference(program.mainMethod, allowNull: true); _flush(); } void writeUriToSource(Program program) { program.uriToSource.keys.forEach((uri) { _sourceUriIndexer.put(uri); }); writeStringTable(_sourceUriIndexer); for (int i = 0; i < _sourceUriIndexer.entries.length; i++) { String uri = _sourceUriIndexer.entries[i].value; Source source = program.uriToSource[uri] ?? new Source([], ''); String sourceCode = source.source; writeStringTableEntry(sourceCode); List lineStarts = source.lineStarts; writeUInt30(lineStarts.length); int previousLineStart = 0; lineStarts.forEach((lineStart) { writeUInt30(lineStart - previousLineStart); previousLineStart = lineStart; }); } } void writeLibraryImportTable(LibraryImportTable imports) { writeList(imports.importPaths, writeStringReference); } void writeLibraryReference(Library node) { int index = _importTable.getImportIndex(node); if (index == -1) { throw 'Missing import for library: ${node.importUri}'; } writeUInt30(index); } void writeClassIndex(Class node) { writeUInt30(_globalIndexer[node]); } void writeClassReference(Class node, {bool allowNull: false}) { if (node == null) { if (allowNull) { writeByte(Tag.NullReference); } else { throw 'Expected a class reference to be valid but was `null`.'; } } else { node.acceptReference(this); } } void writeMemberReference(Member node, {bool allowNull: false}) { if (node == null) { if (allowNull) { writeByte(Tag.NullReference); } else { throw 'Expected a member reference to be valid but was `null`.'; } } else { node.acceptReference(this); } } writeOffset(TreeNode node, int offset) { // TODO(jensj): Delta-encoding. // File offset ranges from -1 and up, // but is here saved as unsigned (thus the +1) writeUInt30(offset + 1); } void visitClassReference(Class node) { var library = node.enclosingLibrary; writeByte(node.isMixinApplication ? Tag.MixinClassReference : Tag.NormalClassReference); writeLibraryReference(library); writeClassIndex(node); } void visitFieldReference(Field node) { if (node.enclosingClass != null) { writeByte(Tag.ClassFieldReference); Class classNode = node.enclosingClass; writeClassReference(classNode); writeUInt30(_globalIndexer[node]); } else { writeByte(Tag.LibraryFieldReference); writeLibraryReference(node.enclosingLibrary); writeUInt30(_globalIndexer[node]); } } void visitConstructorReference(Constructor node) { writeByte(Tag.ClassConstructorReference); writeClassReference(node.enclosingClass); writeUInt30(_globalIndexer[node]); } void visitProcedureReference(Procedure node) { if (node.enclosingClass != null) { writeByte(Tag.ClassProcedureReference); Class classNode = node.enclosingClass; writeClassReference(classNode); writeUInt30(_globalIndexer[node]); } else { writeByte(Tag.LibraryProcedureReference); writeLibraryReference(node.enclosingLibrary); writeUInt30(_globalIndexer[node]); } } void writeName(Name node) { writeStringReference(node.name); // TODO: Consider a more compressed format for private names within the // enclosing library. if (node.isPrivate) { writeLibraryReference(node.library); } } bool insideExternalLibrary = false; visitLibrary(Library node) { insideExternalLibrary = node.isExternal; writeByte(insideExternalLibrary ? 1 : 0); writeStringReference(node.name ?? ''); writeStringReference('${node.importUri}'); // TODO(jensj): We save (almost) the same URI twice. writeUriReference(node.fileUri ?? ''); writeNodeList(node.classes); writeNodeList(node.fields); writeNodeList(node.procedures); } void writeAnnotation(Expression annotation) { _variableIndexer ??= new VariableIndexer(); writeNode(annotation); } void writeAnnotationList(List annotations) { writeList(annotations, writeAnnotation); } visitClass(Class node) { int flags = node.isAbstract ? 1 : 0; if (node.level == ClassLevel.Type) { flags |= 0x2; } if (node.isMixinApplication) { writeByte(Tag.MixinClass); writeOffset(node, node.fileOffset); writeByte(flags); writeStringReference(node.name ?? ''); writeUriReference(node.fileUri ?? ''); writeAnnotationList(node.annotations); _typeParameterIndexer.enter(node.typeParameters); writeNodeList(node.typeParameters); writeNode(node.supertype); writeNode(node.mixedInType); writeNodeList(node.implementedTypes); writeNodeList(node.constructors); _typeParameterIndexer.exit(node.typeParameters); } else { writeByte(Tag.NormalClass); writeOffset(node, node.fileOffset); writeByte(flags); writeStringReference(node.name ?? ''); writeUriReference(node.fileUri ?? ''); writeAnnotationList(node.annotations); _typeParameterIndexer.enter(node.typeParameters); writeNodeList(node.typeParameters); writeOptionalNode(node.supertype); writeNodeList(node.implementedTypes); writeNodeList(node.fields); writeNodeList(node.constructors); writeNodeList(node.procedures); _typeParameterIndexer.exit(node.typeParameters); } } static final Name _emptyName = new Name(''); visitConstructor(Constructor node) { _variableIndexer = new VariableIndexer(); writeByte(Tag.Constructor); writeOffset(node, node.fileOffset); writeOffset(node, node.fileEndOffset); writeByte(node.flags); writeName(node.name ?? _emptyName); writeAnnotationList(node.annotations); assert(node.function.typeParameters.isEmpty); writeNode(node.function); // Parameters are in scope in the initializers. _variableIndexer.restoreScope(node.function.positionalParameters.length + node.function.namedParameters.length); writeNodeList(node.initializers); _variableIndexer = null; } visitProcedure(Procedure node) { _variableIndexer = new VariableIndexer(); writeByte(Tag.Procedure); writeOffset(node, node.fileOffset); writeOffset(node, node.fileEndOffset); writeByte(node.kind.index); writeByte(node.flags); writeName(node.name ?? ''); writeUriReference(node.fileUri ?? ''); writeAnnotationList(node.annotations); writeOptionalNode(node.function); _variableIndexer = null; } visitField(Field node) { _variableIndexer = new VariableIndexer(); writeByte(Tag.Field); writeOffset(node, node.fileOffset); writeOffset(node, node.fileEndOffset); writeByte(node.flags); writeName(node.name ?? ''); writeUriReference(node.fileUri ?? ''); writeAnnotationList(node.annotations); writeNode(node.type); writeOptionalInferredValue(node.inferredValue); writeOptionalNode(node.initializer); _variableIndexer = null; } visitInvalidInitializer(InvalidInitializer node) { writeByte(Tag.InvalidInitializer); } visitFieldInitializer(FieldInitializer node) { writeByte(Tag.FieldInitializer); writeMemberReference(node.field); writeNode(node.value); } visitSuperInitializer(SuperInitializer node) { writeByte(Tag.SuperInitializer); writeMemberReference(node.target); writeNode(node.arguments); } visitRedirectingInitializer(RedirectingInitializer node) { writeByte(Tag.RedirectingInitializer); writeMemberReference(node.target); writeNode(node.arguments); } visitLocalInitializer(LocalInitializer node) { writeByte(Tag.LocalInitializer); writeVariableDeclaration(node.variable); } visitFunctionNode(FunctionNode node) { assert(_variableIndexer != null); _variableIndexer.pushScope(); var oldLabels = _labelIndexer; _labelIndexer = new LabelIndexer(); var oldCases = _switchCaseIndexer; _switchCaseIndexer = new SwitchCaseIndexer(); // Note: FunctionNode has no tag. _typeParameterIndexer.enter(node.typeParameters); writeOffset(node, node.fileOffset); writeOffset(node, node.fileEndOffset); writeByte(node.asyncMarker.index); writeByte(node.debuggable ? 1 : 0); writeNodeList(node.typeParameters); writeUInt30(node.requiredParameterCount); writeVariableDeclarationList(node.positionalParameters); writeVariableDeclarationList(node.namedParameters); writeNode(node.returnType); writeOptionalInferredValue(node.inferredReturnValue); writeOptionalNode(node.body); _labelIndexer = oldLabels; _switchCaseIndexer = oldCases; _typeParameterIndexer.exit(node.typeParameters); _variableIndexer.popScope(); } visitInvalidExpression(InvalidExpression node) { writeByte(Tag.InvalidExpression); } visitVariableGet(VariableGet node) { assert(_variableIndexer != null); int index = _variableIndexer[node.variable]; assert(index != null); if (index & Tag.SpecializedPayloadMask == index && node.promotedType == null) { writeByte(Tag.SpecializedVariableGet + index); writeOffset(node, node.fileOffset); } else { writeByte(Tag.VariableGet); writeOffset(node, node.fileOffset); writeUInt30(_variableIndexer[node.variable]); writeOptionalNode(node.promotedType); } } visitVariableSet(VariableSet node) { assert(_variableIndexer != null); int index = _variableIndexer[node.variable]; if (index & Tag.SpecializedPayloadMask == index) { writeByte(Tag.SpecializedVariableSet + index); writeOffset(node, node.fileOffset); writeNode(node.value); } else { writeByte(Tag.VariableSet); writeOffset(node, node.fileOffset); writeUInt30(_variableIndexer[node.variable]); writeNode(node.value); } } visitPropertyGet(PropertyGet node) { writeByte(Tag.PropertyGet); writeOffset(node, node.fileOffset); writeNode(node.receiver); writeName(node.name); writeMemberReference(node.interfaceTarget, allowNull: true); } visitPropertySet(PropertySet node) { writeByte(Tag.PropertySet); writeOffset(node, node.fileOffset); writeNode(node.receiver); writeName(node.name); writeNode(node.value); writeMemberReference(node.interfaceTarget, allowNull: true); } visitSuperPropertyGet(SuperPropertyGet node) { writeByte(Tag.SuperPropertyGet); writeName(node.name); writeMemberReference(node.interfaceTarget, allowNull: true); } visitSuperPropertySet(SuperPropertySet node) { writeByte(Tag.SuperPropertySet); writeName(node.name); writeNode(node.value); writeMemberReference(node.interfaceTarget, allowNull: true); } visitDirectPropertyGet(DirectPropertyGet node) { writeByte(Tag.DirectPropertyGet); writeNode(node.receiver); writeMemberReference(node.target); } visitDirectPropertySet(DirectPropertySet node) { writeByte(Tag.DirectPropertySet); writeNode(node.receiver); writeMemberReference(node.target); writeNode(node.value); } visitStaticGet(StaticGet node) { writeByte(Tag.StaticGet); writeOffset(node, node.fileOffset); writeMemberReference(node.target); } visitStaticSet(StaticSet node) { writeByte(Tag.StaticSet); writeMemberReference(node.target); writeNode(node.value); } visitMethodInvocation(MethodInvocation node) { writeByte(Tag.MethodInvocation); writeOffset(node, node.fileOffset); writeNode(node.receiver); writeName(node.name); writeNode(node.arguments); writeMemberReference(node.interfaceTarget, allowNull: true); } visitSuperMethodInvocation(SuperMethodInvocation node) { writeByte(Tag.SuperMethodInvocation); writeOffset(node, node.fileOffset); writeName(node.name); writeNode(node.arguments); writeMemberReference(node.interfaceTarget, allowNull: true); } visitDirectMethodInvocation(DirectMethodInvocation node) { writeByte(Tag.DirectMethodInvocation); writeNode(node.receiver); writeMemberReference(node.target); writeNode(node.arguments); } visitStaticInvocation(StaticInvocation node) { writeByte(node.isConst ? Tag.ConstStaticInvocation : Tag.StaticInvocation); writeOffset(node, node.fileOffset); writeMemberReference(node.target); writeNode(node.arguments); } visitConstructorInvocation(ConstructorInvocation node) { writeByte(node.isConst ? Tag.ConstConstructorInvocation : Tag.ConstructorInvocation); writeOffset(node, node.fileOffset); writeMemberReference(node.target); writeNode(node.arguments); } visitArguments(Arguments node) { writeNodeList(node.types); writeNodeList(node.positional); writeNodeList(node.named); } visitNamedExpression(NamedExpression node) { writeStringReference(node.name); writeNode(node.value); } visitNot(Not node) { writeByte(Tag.Not); writeNode(node.operand); } int logicalOperatorIndex(String operator) { switch (operator) { case '&&': return 0; case '||': return 1; } throw 'Not a logical operator: $operator'; } visitLogicalExpression(LogicalExpression node) { writeByte(Tag.LogicalExpression); writeNode(node.left); writeByte(logicalOperatorIndex(node.operator)); writeNode(node.right); } visitConditionalExpression(ConditionalExpression node) { writeByte(Tag.ConditionalExpression); writeNode(node.condition); writeNode(node.then); writeNode(node.otherwise); writeOptionalNode(node.staticType); } visitStringConcatenation(StringConcatenation node) { writeByte(Tag.StringConcatenation); writeOffset(node, node.fileOffset); writeNodeList(node.expressions); } visitIsExpression(IsExpression node) { writeByte(Tag.IsExpression); writeOffset(node, node.fileOffset); writeNode(node.operand); writeNode(node.type); } visitAsExpression(AsExpression node) { writeByte(Tag.AsExpression); writeNode(node.operand); writeNode(node.type); } visitStringLiteral(StringLiteral node) { writeByte(Tag.StringLiteral); writeStringReference(node.value); } visitIntLiteral(IntLiteral node) { int value = node.value; int biasedValue = value + Tag.SpecializedIntLiteralBias; if (biasedValue >= 0 && biasedValue & Tag.SpecializedPayloadMask == biasedValue) { writeByte(Tag.SpecializedIntLiteral + biasedValue); } else if (value.abs() >> 30 == 0) { if (value < 0) { writeByte(Tag.NegativeIntLiteral); writeUInt30(-value); } else { writeByte(Tag.PositiveIntLiteral); writeUInt30(value); } } else { // TODO: Pick a better format for big int literals. writeByte(Tag.BigIntLiteral); writeStringReference('${node.value}'); } } visitDoubleLiteral(DoubleLiteral node) { // TODO: Pick a better format for double literals. writeByte(Tag.DoubleLiteral); writeStringReference('${node.value}'); } visitBoolLiteral(BoolLiteral node) { writeByte(node.value ? Tag.TrueLiteral : Tag.FalseLiteral); } visitNullLiteral(NullLiteral node) { writeByte(Tag.NullLiteral); } visitSymbolLiteral(SymbolLiteral node) { writeByte(Tag.SymbolLiteral); writeStringReference(node.value); } visitTypeLiteral(TypeLiteral node) { writeByte(Tag.TypeLiteral); writeNode(node.type); } visitThisExpression(ThisExpression node) { writeByte(Tag.ThisExpression); } visitRethrow(Rethrow node) { writeByte(Tag.Rethrow); } visitThrow(Throw node) { writeByte(Tag.Throw); writeOffset(node, node.fileOffset); writeNode(node.expression); } visitListLiteral(ListLiteral node) { writeByte(node.isConst ? Tag.ConstListLiteral : Tag.ListLiteral); writeNode(node.typeArgument); writeNodeList(node.expressions); } visitMapLiteral(MapLiteral node) { writeByte(node.isConst ? Tag.ConstMapLiteral : Tag.MapLiteral); writeOffset(node, node.fileOffset); writeNode(node.keyType); writeNode(node.valueType); writeNodeList(node.entries); } visitMapEntry(MapEntry node) { // Note: there is no tag on MapEntry writeNode(node.key); writeNode(node.value); } visitAwaitExpression(AwaitExpression node) { writeByte(Tag.AwaitExpression); writeNode(node.operand); } visitFunctionExpression(FunctionExpression node) { writeByte(Tag.FunctionExpression); writeNode(node.function); } visitLet(Let node) { writeByte(Tag.Let); writeVariableDeclaration(node.variable); writeNode(node.body); --_variableIndexer.stackHeight; } writeStatementOrEmpty(Statement node) { if (node == null) { writeByte(Tag.EmptyStatement); } else { writeNode(node); } } visitInvalidStatement(InvalidStatement node) { writeByte(Tag.InvalidStatement); } visitExpressionStatement(ExpressionStatement node) { writeByte(Tag.ExpressionStatement); writeNode(node.expression); } visitBlock(Block node) { _variableIndexer.pushScope(); writeByte(Tag.Block); writeNodeList(node.statements); _variableIndexer.popScope(); } visitEmptyStatement(EmptyStatement node) { writeByte(Tag.EmptyStatement); } visitAssertStatement(AssertStatement node) { writeByte(Tag.AssertStatement); writeNode(node.condition); writeOptionalNode(node.message); } visitLabeledStatement(LabeledStatement node) { _labelIndexer.enter(node); writeByte(Tag.LabeledStatement); writeNode(node.body); _labelIndexer.exit(); } visitBreakStatement(BreakStatement node) { writeByte(Tag.BreakStatement); writeUInt30(_labelIndexer[node.target]); } visitWhileStatement(WhileStatement node) { writeByte(Tag.WhileStatement); writeNode(node.condition); writeNode(node.body); } visitDoStatement(DoStatement node) { writeByte(Tag.DoStatement); writeNode(node.body); writeNode(node.condition); } visitForStatement(ForStatement node) { _variableIndexer.pushScope(); writeByte(Tag.ForStatement); writeVariableDeclarationList(node.variables); writeOptionalNode(node.condition); writeNodeList(node.updates); writeNode(node.body); _variableIndexer.popScope(); } visitForInStatement(ForInStatement node) { _variableIndexer.pushScope(); writeByte(node.isAsync ? Tag.AsyncForInStatement : Tag.ForInStatement); writeVariableDeclaration(node.variable); writeNode(node.iterable); writeNode(node.body); _variableIndexer.popScope(); } visitSwitchStatement(SwitchStatement node) { _switchCaseIndexer.enter(node); writeByte(Tag.SwitchStatement); writeNode(node.expression); writeNodeList(node.cases); _switchCaseIndexer.exit(node); } visitSwitchCase(SwitchCase node) { // Note: there is no tag on SwitchCase. writeNodeList(node.expressions); writeByte(node.isDefault ? 1 : 0); writeNode(node.body); } visitContinueSwitchStatement(ContinueSwitchStatement node) { writeByte(Tag.ContinueSwitchStatement); writeUInt30(_switchCaseIndexer[node.target]); } visitIfStatement(IfStatement node) { writeByte(Tag.IfStatement); writeNode(node.condition); writeNode(node.then); writeStatementOrEmpty(node.otherwise); } visitReturnStatement(ReturnStatement node) { writeByte(Tag.ReturnStatement); writeOffset(node, node.fileOffset); writeOptionalNode(node.expression); } visitTryCatch(TryCatch node) { writeByte(Tag.TryCatch); writeNode(node.body); writeNodeList(node.catches); } visitCatch(Catch node) { // Note: there is no tag on Catch. _variableIndexer.pushScope(); writeNode(node.guard); writeOptionalVariableDeclaration(node.exception); writeOptionalVariableDeclaration(node.stackTrace); writeNode(node.body); _variableIndexer.popScope(); } visitTryFinally(TryFinally node) { writeByte(Tag.TryFinally); writeNode(node.body); writeNode(node.finalizer); } visitYieldStatement(YieldStatement node) { writeByte(Tag.YieldStatement); writeOffset(node, node.fileOffset); writeByte(node.flags); writeNode(node.expression); } visitVariableDeclaration(VariableDeclaration node) { writeByte(Tag.VariableDeclaration); writeVariableDeclaration(node); } void writeVariableDeclaration(VariableDeclaration node) { writeOffset(node, node.fileOffset); writeByte(node.flags); writeStringReference(node.name ?? ''); writeNode(node.type); writeOptionalInferredValue(node.inferredValue); writeOptionalNode(node.initializer); // Declare the variable after its initializer. It is not in scope in its // own initializer. _variableIndexer.declare(node); } void writeVariableDeclarationList(List nodes) { writeList(nodes, writeVariableDeclaration); } void writeOptionalVariableDeclaration(VariableDeclaration node) { if (node == null) { writeByte(Tag.Nothing); } else { writeByte(Tag.Something); writeVariableDeclaration(node); } } visitFunctionDeclaration(FunctionDeclaration node) { writeByte(Tag.FunctionDeclaration); writeOffset(node, node.fileOffset); writeVariableDeclaration(node.variable); writeNode(node.function); } visitBottomType(BottomType node) { writeByte(Tag.BottomType); } visitInvalidType(InvalidType node) { writeByte(Tag.InvalidType); } visitDynamicType(DynamicType node) { writeByte(Tag.DynamicType); } visitVoidType(VoidType node) { writeByte(Tag.VoidType); } visitInterfaceType(InterfaceType node) { if (node.typeArguments.isEmpty) { writeByte(Tag.SimpleInterfaceType); writeClassReference(node.classNode); } else { writeByte(Tag.InterfaceType); writeClassReference(node.classNode); writeNodeList(node.typeArguments); } } visitSupertype(Supertype node) { if (node.typeArguments.isEmpty) { writeByte(Tag.SimpleInterfaceType); writeClassReference(node.classNode); } else { writeByte(Tag.InterfaceType); writeClassReference(node.classNode); writeNodeList(node.typeArguments); } } visitFunctionType(FunctionType node) { if (node.requiredParameterCount == node.positionalParameters.length && node.typeParameters.isEmpty && node.namedParameters.isEmpty) { writeByte(Tag.SimpleFunctionType); writeNodeList(node.positionalParameters); writeNode(node.returnType); } else { writeByte(Tag.FunctionType); _typeParameterIndexer.enter(node.typeParameters); writeNodeList(node.typeParameters); writeUInt30(node.requiredParameterCount); writeNodeList(node.positionalParameters); writeNodeList(node.namedParameters); writeNode(node.returnType); _typeParameterIndexer.exit(node.typeParameters); } } visitNamedType(NamedType node) { writeStringReference(node.name); writeNode(node.type); } visitTypeParameterType(TypeParameterType node) { writeByte(Tag.TypeParameterType); writeUInt30(_typeParameterIndexer[node.parameter]); } visitTypeParameter(TypeParameter node) { writeStringReference(node.name ?? ''); writeNode(node.bound); } defaultNode(Node node) { throw 'Unsupported node: $node'; } } class VariableIndexer { final Map index = {}; final List scopes = []; int stackHeight = 0; void declare(VariableDeclaration node) { index[node] = stackHeight++; } void pushScope() { scopes.add(stackHeight); } void popScope() { stackHeight = scopes.removeLast(); } void restoreScope(int numberOfVariables) { stackHeight += numberOfVariables; } int operator [](VariableDeclaration node) { return index[node]; } } class LabelIndexer { final Map index = {}; int stackHeight = 0; void enter(LabeledStatement node) { index[node] = stackHeight++; } void exit() { --stackHeight; } int operator [](LabeledStatement node) => index[node]; } class SwitchCaseIndexer { final Map index = {}; int stackHeight = 0; void enter(SwitchStatement node) { for (var caseNode in node.cases) { index[caseNode] = stackHeight++; } } void exit(SwitchStatement node) { stackHeight -= node.cases.length; } int operator [](SwitchCase node) => index[node]; } class TypeParameterIndexer { final Map index = {}; int stackHeight = 0; void enter(List typeParameters) { for (var parameter in typeParameters) { index[parameter] = stackHeight; ++stackHeight; } } void exit(List typeParameters) { stackHeight -= typeParameters.length; } int operator [](TypeParameter parameter) => index[parameter]; } class StringTableEntry implements Comparable { final String value; int frequency = 0; StringTableEntry(this.value); int compareTo(StringTableEntry other) => other.frequency - frequency; } class StringIndexer extends RecursiveVisitor { final List entries = []; final LinkedHashMap index = new LinkedHashMap(); StringIndexer() { put(''); } int get numberOfStrings => index.length; void build(Node node) { node.accept(this); entries.sort(); for (int i = 0; i < entries.length; ++i) { index[entries[i].value] = i; } } void put(String string) { int i = index.putIfAbsent(string, () { entries.add(new StringTableEntry(string)); return index.length; }); ++entries[i].frequency; } void putOptional(String string) { if (string != null) { put(string); } } int operator [](String string) => index[string]; void addLibraryImports(LibraryImportTable imports) { imports.importPaths.forEach(put); } visitName(Name node) { put(node.name); } visitLibrary(Library node) { putOptional(node.name); put('${node.importUri}'); node.visitChildren(this); } visitClass(Class node) { putOptional(node.name); node.visitChildren(this); } visitNamedExpression(NamedExpression node) { put(node.name); node.visitChildren(this); } visitStringLiteral(StringLiteral node) { put(node.value); } visitIntLiteral(IntLiteral node) { if (node.value.abs() >> 30 != 0) { put('${node.value}'); } } visitDoubleLiteral(DoubleLiteral node) { put('${node.value}'); } visitSymbolLiteral(SymbolLiteral node) { put(node.value); } visitVariableDeclaration(VariableDeclaration node) { putOptional(node.name); node.visitChildren(this); } visitNamedType(NamedType node) { put(node.name); node.visitChildren(this); } visitTypeParameter(TypeParameter node) { putOptional(node.name); node.visitChildren(this); } } /// Computes and stores the index of a library, class, or member within its /// parent list. class GlobalIndexer extends TreeVisitor { final Map indices = {}; void buildIndexForContainer(TreeNode libraryOrClass) { libraryOrClass.accept(this); } void buildIndexForList(List list) { for (int i = 0; i < list.length; ++i) { TreeNode child = list[i]; if (child != null) { indices[child] = i; } } } visitProgram(Program node) { buildIndexForList(node.libraries); } visitLibrary(Library node) { buildIndexForList(node.classes); buildIndexForList(node.fields); buildIndexForList(node.procedures); } visitClass(Class node) { buildIndexForList(node.fields); buildIndexForList(node.constructors); buildIndexForList(node.procedures); } int operator [](TreeNode memberOrLibraryOrClass) { var node = memberOrLibraryOrClass; assert(node is Member || node is Library || node is Class); int index = indices[node]; if (index == null) { buildIndexForContainer(node.parent); return indices[node]; } else { return index; } } } /// Puts a buffer in front of a [Sink>]. class BufferedSink { static const int SIZE = 100000; static const int SMALL = 10000; final Sink> _sink; Uint8List _buffer = new Uint8List(SIZE); int length = 0; BufferedSink(this._sink); void addByte(int byte) { _buffer[length++] = byte; if (length == SIZE) { _sink.add(_buffer); _buffer = new Uint8List(SIZE); length = 0; } } void addBytes(List bytes) { // Avoid copying a large buffer into the another large buffer. Also, if // the bytes buffer is too large to fit in our own buffer, just emit both. if (length + bytes.length < SIZE && (bytes.length < SMALL || length < SMALL)) { if (length == 0) { _sink.add(bytes); } else { _buffer.setRange(length, length + bytes.length, bytes); length += bytes.length; } } else if (bytes.length < SMALL) { // Flush as much as we can in the current buffer. _buffer.setRange(length, SIZE, bytes); _sink.add(_buffer); // Copy over the remainder into a new buffer. It is guaranteed to fit // because the input byte array is small. int alreadyEmitted = SIZE - length; int remainder = bytes.length - alreadyEmitted; _buffer = new Uint8List(SIZE); _buffer.setRange(0, remainder, bytes, alreadyEmitted); length = remainder; } else { _sink.add(_buffer.sublist(0, length)); _sink.add(bytes); _buffer = new Uint8List(SIZE); length = 0; } } void flush() { _sink.add(_buffer.sublist(0, length)); _buffer = new Uint8List(SIZE); length = 0; } void flushAndDestroy() { _sink.add(_buffer.sublist(0, length)); } }