blob: 7a86c70980a00dee61971a0998c95c4f5bf6de78 [file] [log] [blame]
// 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, 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<ClosureDeclaration> 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<PackedObject> 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<int> bytecodes;
final ExceptionsTable exceptionsTable;
final SourcePositions sourcePositions;
final List<ObjectHandle> nullableFields;
final List<ClosureDeclaration> 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) {
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));
}
}
factory MemberBytecode.read(BufferedReader reader) {
int flags = reader.readPackedUInt30();
final List<ClosureDeclaration> closures = ((flags & hasClosuresFlag) != 0)
? new List<ClosureDeclaration>.generate(reader.readPackedUInt30(),
(_) => new ClosureDeclaration.read(reader))
: const <ClosureDeclaration>[];
final ConstantPool constantPool = new ConstantPool.read(reader);
final List<int> 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<ObjectHandle> nullableFields =
((flags & hasNullableFieldsFlag) != 0)
? reader.readPackedList<ObjectHandle>()
: const <ObjectHandle>[];
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<NameAndType> typeParams;
final int numRequiredParams;
final int numNamedParams;
final List<NameAndType> 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<NameAndType> typeParams;
if ((flags & flagHasTypeParams) != 0) {
final int numTypeParams = reader.readPackedUInt30();
List<ObjectHandle> names = new List<ObjectHandle>.generate(
numTypeParams, (_) => reader.readPackedObject());
List<ObjectHandle> bounds = new List<ObjectHandle>.generate(
numTypeParams, (_) => reader.readPackedObject());
typeParams = new List<NameAndType>.generate(
numTypeParams, (int i) => new NameAndType(names[i], bounds[i]));
} else {
typeParams = const <NameAndType>[];
}
final numParams = reader.readPackedUInt30();
final numRequiredParams = (flags &
(flagHasOptionalPositionalParams |
flagHasOptionalNamedParams) !=
0)
? reader.readPackedUInt30()
: numParams;
final numNamedParams = (flags & flagHasOptionalNamedParams != 0)
? (numParams - numRequiredParams)
: 0;
final List<NameAndType> parameters = new List<NameAndType>.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<int> 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<int> 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) {
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());
}
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<BytecodeMetadata> {
@override
final String tag = 'vm.bytecode';
@override
final Map<TreeNode, BytecodeMetadata> mapping =
<TreeNode, BytecodeMetadata>{};
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<int> bytecodes) {
writer.writePackedUInt30(bytecodes.length);
writer.align(bytecodeInstructionsAlignment);
writer.writeBytes(bytecodes);
}
List<int> _readBytecodeInstructions(BufferedReader reader) {
int len = reader.readPackedUInt30();
reader.align(bytecodeInstructionsAlignment);
return reader.readBytesAsUint8List(len);
}