// Copyright (c) 2018, 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.metadata.bytecode; import 'package:kernel/ast.dart'; import '../bytecode/bytecode_serialization.dart' show BufferedWriter, BufferedReader, BytecodeSizeStatistics, StringTable; import '../bytecode/constant_pool.dart' show ConstantPool; import '../bytecode/dbc.dart' show stableBytecodeFormatVersion, futureBytecodeFormatVersion, bytecodeInstructionsAlignment; import '../bytecode/disassembler.dart' show BytecodeDisassembler; import '../bytecode/exceptions.dart' show ExceptionsTable; import '../bytecode/object_table.dart' show ObjectTable, ObjectHandle, NameAndType; import '../bytecode/source_positions.dart' show SourcePositions; abstract class BytecodeMetadata { void write(BufferedWriter writer); } /// Bytecode of a member is encoded in the following way: /// /// type MemberBytecode { /// UInt flags (HasExceptionsTable, HasSourcePositions, HasNullableFields, /// HasClosures) /// /// (optional, present if HasClosures) /// List closureDeclarations /// /// ConstantPool constantPool /// /// UInt bytecodeSizeInBytes /// Byte[] padding /// Byte[bytecodeSizeInBytes] bytecodes /// /// (optional, present if HasExceptionsTable) /// ExceptionsTable exceptionsTable /// /// (optional, present if HasSourcePositions) /// SourcePositions sourcePositionsTabe /// /// (optional, present if HasNullableFields) /// List nullableFields /// /// (optional, present if HasClosures) /// ClosureBytecode[] closures /// } /// /// type ClosureDeclaration { /// UInt flags (hasOptionalPositionalParams, hasOptionalNamedParams, /// hasTypeParams) /// /// PackedObject parent // Member or Closure /// PackedObject name /// /// if hasTypeParams /// UInt numTypeParameters /// PackedObject[numTypeParameters] typeParameterNames /// PackedObject[numTypeParameters] typeParameterBounds /// /// UInt numParameters /// /// if hasOptionalPositionalParams || hasOptionalNamedParams /// UInt numRequiredParameters /// /// NameAndType[numParameters] parameters /// PackedObject returnType /// } /// /// type ClosureBytecode { /// UInt flags (HasExceptionsTable, HasSourcePositions) /// /// UInt bytecodeSizeInBytes /// Byte[] padding /// Byte[bytecodeSizeInBytes] bytecodes /// /// (optional, present if HasExceptionsTable) /// ExceptionsTable exceptionsTable /// /// (optional, present if HasSourcePositions) /// SourcePositions sourcePositionsTabe /// } /// /// Encoding of ExceptionsTable is described in /// pkg/vm/lib/bytecode/exceptions.dart. /// /// Encoding of ConstantPool is described in /// pkg/vm/lib/bytecode/constant_pool.dart. /// class MemberBytecode extends BytecodeMetadata { static const hasExceptionsTableFlag = 1 << 0; static const hasSourcePositionsFlag = 1 << 1; static const hasNullableFieldsFlag = 1 << 2; static const hasClosuresFlag = 1 << 3; final ConstantPool constantPool; final List bytecodes; final ExceptionsTable exceptionsTable; final SourcePositions sourcePositions; final List nullableFields; final List closures; bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty; bool get hasSourcePositions => sourcePositions.mapping.isNotEmpty; bool get hasNullableFields => nullableFields.isNotEmpty; bool get hasClosures => closures.isNotEmpty; int get flags => (hasExceptionsTable ? hasExceptionsTableFlag : 0) | (hasSourcePositions ? hasSourcePositionsFlag : 0) | (hasNullableFields ? hasNullableFieldsFlag : 0) | (hasClosures ? hasClosuresFlag : 0); MemberBytecode(this.constantPool, this.bytecodes, this.exceptionsTable, this.sourcePositions, this.nullableFields, this.closures); @override void write(BufferedWriter writer) { final start = writer.offset; writer.writePackedUInt30(flags); if (hasClosures) { writer.writePackedUInt30(closures.length); closures.forEach((c) => c.write(writer)); } constantPool.write(writer); _writeBytecodeInstructions(writer, bytecodes); if (hasExceptionsTable) { exceptionsTable.write(writer); } if (hasSourcePositions) { sourcePositions.write(writer); } if (hasNullableFields) { writer.writePackedList(nullableFields); } if (hasClosures) { closures.forEach((c) => c.bytecode.write(writer)); } BytecodeSizeStatistics.membersSize += (writer.offset - start); } factory MemberBytecode.read(BufferedReader reader) { int flags = reader.readPackedUInt30(); final List closures = ((flags & hasClosuresFlag) != 0) ? new List.generate(reader.readPackedUInt30(), (_) => new ClosureDeclaration.read(reader)) : const []; final ConstantPool constantPool = new ConstantPool.read(reader); final List bytecodes = _readBytecodeInstructions(reader); final exceptionsTable = ((flags & hasExceptionsTableFlag) != 0) ? new ExceptionsTable.read(reader) : new ExceptionsTable(); final sourcePositions = ((flags & hasSourcePositionsFlag) != 0) ? new SourcePositions.read(reader) : new SourcePositions(); final List nullableFields = ((flags & hasNullableFieldsFlag) != 0) ? reader.readPackedList() : const []; for (var c in closures) { c.bytecode = new ClosureBytecode.read(reader); } return new MemberBytecode(constantPool, bytecodes, exceptionsTable, sourcePositions, nullableFields, closures); } // TODO(alexmarkov): Consider printing constant pool before bytecode. @override String toString() => "\n" "Bytecode {\n" "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable, annotations: [ sourcePositions.getBytecodeAnnotations() ])}}\n" "$exceptionsTable" "${nullableFields.isEmpty ? '' : 'Nullable fields: $nullableFields}\n'}" "$constantPool" "${closures.join('\n')}"; } class ClosureDeclaration { static const int flagHasOptionalPositionalParams = 1 << 0; static const int flagHasOptionalNamedParams = 1 << 1; static const int flagHasTypeParams = 1 << 2; final ObjectHandle parent; final ObjectHandle name; final List typeParams; final int numRequiredParams; final int numNamedParams; final List parameters; final ObjectHandle returnType; ClosureBytecode bytecode; ClosureDeclaration( this.parent, this.name, this.typeParams, this.numRequiredParams, this.numNamedParams, this.parameters, this.returnType); void write(BufferedWriter writer) { int flags = 0; if (numRequiredParams != parameters.length) { if (numNamedParams > 0) { flags |= flagHasOptionalNamedParams; } else { flags |= flagHasOptionalPositionalParams; } } if (typeParams.isNotEmpty) { flags |= flagHasTypeParams; } writer.writePackedUInt30(flags); writer.writePackedObject(parent); writer.writePackedObject(name); if (flags & flagHasTypeParams != 0) { writer.writePackedUInt30(typeParams.length); for (var tp in typeParams) { writer.writePackedObject(tp.name); } for (var tp in typeParams) { writer.writePackedObject(tp.type); } } writer.writePackedUInt30(parameters.length); if (flags & (flagHasOptionalPositionalParams | flagHasOptionalNamedParams) != 0) { writer.writePackedUInt30(numRequiredParams); } for (var param in parameters) { writer.writePackedObject(param.name); writer.writePackedObject(param.type); } writer.writePackedObject(returnType); } factory ClosureDeclaration.read(BufferedReader reader) { final int flags = reader.readPackedUInt30(); final parent = reader.readPackedObject(); final name = reader.readPackedObject(); List typeParams; if ((flags & flagHasTypeParams) != 0) { final int numTypeParams = reader.readPackedUInt30(); List names = new List.generate( numTypeParams, (_) => reader.readPackedObject()); List bounds = new List.generate( numTypeParams, (_) => reader.readPackedObject()); typeParams = new List.generate( numTypeParams, (int i) => new NameAndType(names[i], bounds[i])); } else { typeParams = const []; } final numParams = reader.readPackedUInt30(); final numRequiredParams = (flags & (flagHasOptionalPositionalParams | flagHasOptionalNamedParams) != 0) ? reader.readPackedUInt30() : numParams; final numNamedParams = (flags & flagHasOptionalNamedParams != 0) ? (numParams - numRequiredParams) : 0; final List parameters = new List.generate( numParams, (_) => new NameAndType( reader.readPackedObject(), reader.readPackedObject())); final returnType = reader.readPackedObject(); return new ClosureDeclaration(parent, name, typeParams, numRequiredParams, numNamedParams, parameters, returnType); } @override String toString() { StringBuffer sb = new StringBuffer(); sb.write('Closure $parent::$name'); if (typeParams.isNotEmpty) { sb.write(' <${typeParams.join(', ')}>'); } sb.write(' ('); sb.write(parameters.sublist(0, numRequiredParams).join(', ')); if (numRequiredParams != parameters.length) { if (numRequiredParams > 0) { sb.write(', '); } if (numNamedParams > 0) { sb.write('{ ${parameters.sublist(numRequiredParams).join(', ')} }'); } else { sb.write('[ ${parameters.sublist(numRequiredParams).join(', ')} ]'); } } sb.write(') -> '); sb.writeln(returnType); if (bytecode != null) { sb.write(bytecode.toString()); } return sb.toString(); } } /// Bytecode of a nested function (closure). /// Closures share the constant pool of a top-level member. class ClosureBytecode { final List bytecodes; final ExceptionsTable exceptionsTable; final SourcePositions sourcePositions; bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty; bool get hasSourcePositions => sourcePositions.mapping.isNotEmpty; int get flags => (hasExceptionsTable ? MemberBytecode.hasExceptionsTableFlag : 0) | (hasSourcePositions ? MemberBytecode.hasSourcePositionsFlag : 0); ClosureBytecode(this.bytecodes, this.exceptionsTable, this.sourcePositions); void write(BufferedWriter writer) { writer.writePackedUInt30(flags); _writeBytecodeInstructions(writer, bytecodes); if (hasExceptionsTable) { exceptionsTable.write(writer); } if (hasSourcePositions) { sourcePositions.write(writer); } } factory ClosureBytecode.read(BufferedReader reader) { final int flags = reader.readPackedUInt30(); final List bytecodes = _readBytecodeInstructions(reader); final exceptionsTable = ((flags & MemberBytecode.hasExceptionsTableFlag) != 0) ? new ExceptionsTable.read(reader) : new ExceptionsTable(); final sourcePositions = ((flags & MemberBytecode.hasSourcePositionsFlag) != 0) ? new SourcePositions.read(reader) : new SourcePositions(); return new ClosureBytecode(bytecodes, exceptionsTable, sourcePositions); } @override String toString() { StringBuffer sb = new StringBuffer(); sb.writeln('ClosureBytecode {'); sb.writeln(new BytecodeDisassembler().disassemble( bytecodes, exceptionsTable, annotations: [sourcePositions.getBytecodeAnnotations()])); sb.writeln('}'); return sb.toString(); } } class BytecodeComponent extends BytecodeMetadata { int version; StringTable stringTable; ObjectTable objectTable; BytecodeComponent(this.version) : stringTable = new StringTable(), objectTable = new ObjectTable(); @override void write(BufferedWriter writer) { final start = writer.offset; objectTable.allocateIndexTable(); // Writing object table may add new strings to strings table, // so serialize object table first. BufferedWriter objectsWriter = new BufferedWriter.fromWriter(writer); objectTable.write(objectsWriter); BufferedWriter stringsWriter = new BufferedWriter.fromWriter(writer); stringTable.write(stringsWriter); writer.writePackedUInt30(version); writer.writePackedUInt30(stringsWriter.offset); writer.writePackedUInt30(objectsWriter.offset); writer.writeBytes(stringsWriter.takeBytes()); writer.writeBytes(objectsWriter.takeBytes()); BytecodeSizeStatistics.componentSize += (writer.offset - start); } BytecodeComponent.read(BufferedReader reader) { version = reader.readPackedUInt30(); if (version != stableBytecodeFormatVersion && version != futureBytecodeFormatVersion) { throw 'Error: unexpected bytecode version $version'; } reader.formatVersion = version; reader.readPackedUInt30(); // Strings size reader.readPackedUInt30(); // Objects size stringTable = new StringTable.read(reader); reader.stringReader = stringTable; objectTable = new ObjectTable.read(reader); reader.objectReader = objectTable; } String toString() => "\n" "Bytecode" " (version: " "${version == stableBytecodeFormatVersion ? 'stable' : version == futureBytecodeFormatVersion ? 'future' : "v$version"}" ")\n" // "$objectTable\n" // "$stringTable\n" ; } /// Repository for [BytecodeMetadata]. class BytecodeMetadataRepository extends MetadataRepository { @override final String tag = 'vm.bytecode'; @override final Map mapping = {}; BytecodeComponent bytecodeComponent; @override void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) { if (node is Component) { bytecodeComponent = metadata as BytecodeComponent; } else { assert(bytecodeComponent != null); } final writer = new BufferedWriter(bytecodeComponent.version, bytecodeComponent.stringTable, bytecodeComponent.objectTable, baseOffset: sink.getBufferOffset()); metadata.write(writer); sink.writeBytes(writer.takeBytes()); } @override BytecodeMetadata readFromBinary(Node node, BinarySource source) { if (node is Component) { final reader = new BufferedReader(-1, null, null, source.bytes, baseOffset: source.currentOffset); bytecodeComponent = new BytecodeComponent.read(reader); return bytecodeComponent; } else { final reader = new BufferedReader( bytecodeComponent.version, bytecodeComponent.stringTable, bytecodeComponent.objectTable, source.bytes, baseOffset: source.currentOffset); return new MemberBytecode.read(reader); } } } void _writeBytecodeInstructions(BufferedWriter writer, List bytecodes) { writer.writePackedUInt30(bytecodes.length); writer.align(bytecodeInstructionsAlignment); writer.writeBytes(bytecodes); BytecodeSizeStatistics.instructionsSize += bytecodes.length; } List _readBytecodeInstructions(BufferedReader reader) { int len = reader.readPackedUInt30(); reader.align(bytecodeInstructionsAlignment); return reader.readBytesAsUint8List(len); }