Files
sdk/pkg/vm/lib/metadata/bytecode.dart
T
Alexander Markov 9f49b47823 [vm/bytecode] Revise representation of objects and strings in bytecode
This is a major revamp of bytecode metadata format. Now bytecode has
its own serialization mechanisms.

This CL adds 'bytecode component' metadata, which contains bytecode
object table and string table. All references from bytecode (constant
pools) to libraries, classes, members, types and strings now have a new
format. References to frequently used objects are represented as indices
in object table, while rarely used objects are written inline.
This allows VM to cache frequently used objects while reading bytecode.

Representation of strings is aligned with VM - string characters are
stored in separate pools of one-byte and two-byte strings. This allows
VM to avoid UTF-8 decoding and extra copying.

Closure declarations are now explicit. Type parameters no longer require
enslosing scopes when reading/writing them.

Benchmarks:
GenKernelKernelReadAllBytecode (Intel Core i5) +29.84%
GenKernelKernelReadAllBytecode (Intel Xeon) +28.74%
Change-Id: I4b80009733a8f8c038264af74f97c4e094b9e311
Reviewed-on: https://dart-review.googlesource.com/c/85469
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
2018-12-03 19:08:42 +00:00

471 lines
15 KiB
Dart

// 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);
}