// 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/constant_pool.dart' show ConstantPool; import '../bytecode/dbc.dart' show stableBytecodeFormatVersion, futureBytecodeFormatVersion; import '../bytecode/disassembler.dart' show BytecodeDisassembler; import '../bytecode/exceptions.dart' show ExceptionsTable; import '../bytecode/source_positions.dart' show SourcePositions; /// Metadata containing bytecode. /// /// In kernel binary, bytecode metadata is encoded as following: /// /// type BytecodeMetadata { /// UInt bytecodeFormatVersion /// UInt flags (HasExceptionsTable, HasSourcePositions, HasNullableFields, /// HasClosures) /// ConstantPool constantPool /// /// UInt bytecodeSizeInBytes /// Byte paddingSizeInBytes /// Byte[paddingSizeInBytes] 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) /// List closures /// } /// /// type ClosureBytecode { /// ConstantIndex closureFunction /// UInt flags (HasExceptionsTable, HasSourcePositions) /// /// UInt bytecodeSizeInBytes /// Byte paddingSizeInBytes /// Byte[paddingSizeInBytes] 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 BytecodeMetadata { static const hasExceptionsTableFlag = 1 << 0; static const hasSourcePositionsFlag = 1 << 1; static const hasNullableFieldsFlag = 1 << 2; static const hasClosuresFlag = 1 << 3; final int version; 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); BytecodeMetadata( this.version, this.constantPool, this.bytecodes, this.exceptionsTable, this.sourcePositions, this.nullableFields, this.closures); // TODO(alexmarkov): Consider printing constant pool before bytecode. @override String toString() => "\n" "Bytecode" " (version: " "${version == stableBytecodeFormatVersion ? 'stable' : version == futureBytecodeFormatVersion ? 'future' : "v$version"}" ") {\n" "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable, annotations: [ sourcePositions.getBytecodeAnnotations() ])}}\n" "$exceptionsTable" "${nullableFields.isEmpty ? '' : 'Nullable fields: ${nullableFields.map((ref) => ref.asField).toList()}\n'}" "$constantPool" "${closures.join('\n')}"; } /// Bytecode of a nested function (closure). /// Closures share the constant pool of a top-level member. class ClosureBytecode { final int closureFunctionConstantIndex; 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 ? BytecodeMetadata.hasExceptionsTableFlag : 0) | (hasSourcePositions ? BytecodeMetadata.hasSourcePositionsFlag : 0); ClosureBytecode(this.closureFunctionConstantIndex, this.bytecodes, this.exceptionsTable, this.sourcePositions); void writeToBinary(BinarySink sink) { sink.writeUInt30(closureFunctionConstantIndex); sink.writeUInt30(flags); _writeBytecodeInstructions(sink, bytecodes); if (hasExceptionsTable) { exceptionsTable.writeToBinary(sink); } if (hasSourcePositions) { sink.writeByteList(sourcePositions.toBytes()); } } factory ClosureBytecode.readFromBinary(BinarySource source) { final closureFunctionConstantIndex = source.readUInt(); final int flags = source.readUInt(); final List bytecodes = _readBytecodeInstructions(source); final exceptionsTable = ((flags & BytecodeMetadata.hasExceptionsTableFlag) != 0) ? new ExceptionsTable.readFromBinary(source) : new ExceptionsTable(); final sourcePositions = ((flags & BytecodeMetadata.hasSourcePositionsFlag) != 0) ? new SourcePositions.fromBytes(source.readByteList()) : new SourcePositions(); return new ClosureBytecode(closureFunctionConstantIndex, bytecodes, exceptionsTable, sourcePositions); } @override String toString() { StringBuffer sb = new StringBuffer(); sb.writeln('Closure CP#$closureFunctionConstantIndex {'); sb.writeln(new BytecodeDisassembler().disassemble( bytecodes, exceptionsTable, annotations: [sourcePositions.getBytecodeAnnotations()])); sb.writeln('}'); return sb.toString(); } } /// Repository for [BytecodeMetadata]. class BytecodeMetadataRepository extends MetadataRepository { @override final String tag = 'vm.bytecode'; @override final Map mapping = {}; @override void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) { sink.writeUInt30(metadata.version); sink.writeUInt30(metadata.flags); metadata.constantPool.writeToBinary(node, sink); _writeBytecodeInstructions(sink, metadata.bytecodes); if (metadata.hasExceptionsTable) { metadata.exceptionsTable.writeToBinary(sink); } if (metadata.hasSourcePositions) { sink.writeByteList(metadata.sourcePositions.toBytes()); } if (metadata.hasNullableFields) { sink.writeUInt30(metadata.nullableFields.length); metadata.nullableFields.forEach((ref) => sink .writeCanonicalNameReference(getCanonicalNameOfMember(ref.asField))); } if (metadata.hasClosures) { sink.writeUInt30(metadata.closures.length); metadata.closures.forEach((c) => c.writeToBinary(sink)); } } @override BytecodeMetadata readFromBinary(Node node, BinarySource source) { int version = source.readUInt(); if (version != stableBytecodeFormatVersion && version != futureBytecodeFormatVersion) { throw 'Error: unexpected bytecode version $version'; } int flags = source.readUInt(); final ConstantPool constantPool = new ConstantPool.readFromBinary(node, source); final List bytecodes = _readBytecodeInstructions(source); final exceptionsTable = ((flags & BytecodeMetadata.hasExceptionsTableFlag) != 0) ? new ExceptionsTable.readFromBinary(source) : new ExceptionsTable(); final sourcePositions = ((flags & BytecodeMetadata.hasSourcePositionsFlag) != 0) ? new SourcePositions.fromBytes(source.readByteList()) : new SourcePositions(); final List nullableFields = ((flags & BytecodeMetadata.hasNullableFieldsFlag) != 0) ? new List.generate(source.readUInt(), (_) => source.readCanonicalNameReference().getReference()) : const []; final List closures = ((flags & BytecodeMetadata.hasClosuresFlag) != 0) ? new List.generate(source.readUInt(), (_) => new ClosureBytecode.readFromBinary(source)) : const []; return new BytecodeMetadata(version, constantPool, bytecodes, exceptionsTable, sourcePositions, nullableFields, closures); } } void _writeBytecodeInstructions(BinarySink sink, List bytecodes) { sink.writeUInt30(bytecodes.length); _writeBytecodePadding(sink); sink.writeBytes(bytecodes); } List _readBytecodeInstructions(BinarySource source) { int len = source.readUInt(); _readBytecodePadding(source); return source.readBytes(len); } void _writeBytecodePadding(BinarySink sink) { const int bytecodeAlignment = 4; int offset = sink.getBufferOffset() + 1; // +1 is for the length. int len = ((offset + bytecodeAlignment - 1) & -bytecodeAlignment) - offset; sink.writeByte(len); for (int i = 0; i < len; ++i) { sink.writeByte(0); } assert((sink.getBufferOffset() & (bytecodeAlignment - 1)) == 0); } void _readBytecodePadding(BinarySource source) { int len = source.readByte(); for (int i = 0; i < len; ++i) { source.readByte(); } }