Files
sdk/pkg/vm/lib/bytecode/bytecode_serialization.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

361 lines
9.5 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.bytecode.bytecode_serialization;
import 'dart:io' show BytesBuilder;
import 'dart:typed_data' show Uint8List, Uint16List;
abstract class StringWriter {
int put(String string);
}
abstract class StringReader {
String get(int ref);
}
abstract class BytecodeObject {}
abstract class ObjectWriter {
void writeObject(BytecodeObject object, BufferedWriter writer);
}
abstract class ObjectReader {
BytecodeObject readObject(BufferedReader reader);
}
class BufferedWriter {
final int formatVersion;
final StringWriter stringWriter;
final ObjectWriter objectWriter;
final BytesBuilder bytes = new BytesBuilder();
final int baseOffset;
BufferedWriter(this.formatVersion, this.stringWriter, this.objectWriter,
{this.baseOffset: 0});
factory BufferedWriter.fromWriter(BufferedWriter writer) =>
new BufferedWriter(
writer.formatVersion, writer.stringWriter, writer.objectWriter);
List<int> takeBytes() => bytes.takeBytes();
int get offset => bytes.length;
void writeByte(int value) {
assert((value >> 8) == 0);
bytes.addByte(value);
}
void writeBytes(List<int> values) {
bytes.add(values);
}
void writeUInt32(int value) {
if ((value >> 32) != 0) {
throw 'Unable to write $value as 32-bit unsigned integer';
}
// TODO(alexmarkov): consider using native byte order
bytes.addByte((value >> 24) & 0xFF);
bytes.addByte((value >> 16) & 0xFF);
bytes.addByte((value >> 8) & 0xFF);
bytes.addByte(value & 0xFF);
}
void writePackedUInt30(int value) {
if ((value >> 30) != 0) {
throw 'Unable to write $value as 30-bit unsigned integer';
}
if (value < 0x80) {
bytes.addByte(value);
} else if (value < 0x4000) {
bytes.addByte((value >> 8) | 0x80);
bytes.addByte(value & 0xFF);
} else {
bytes.addByte((value >> 24) | 0xC0);
bytes.addByte((value >> 16) & 0xFF);
bytes.addByte((value >> 8) & 0xFF);
bytes.addByte(value & 0xFF);
}
}
void writeSLEB128(int value) {
bool last = false;
do {
int part = value & 0x7f;
value >>= 7;
if ((value == 0 && (part & 0x40) == 0) ||
(value == -1 && (part & 0x40) != 0)) {
last = true;
} else {
part |= 0x80;
}
bytes.addByte(part);
} while (!last);
}
void writePackedStringReference(String value) {
writePackedUInt30(stringWriter.put(value));
}
void writePackedObject(BytecodeObject object) {
objectWriter.writeObject(object, this);
}
void writePackedList(List<BytecodeObject> objects) {
writePackedUInt30(objects.length);
for (var obj in objects) {
writePackedObject(obj);
}
}
void align(int alignment) {
assert(alignment & (alignment - 1) == 0);
int offs = baseOffset + offset;
int padding = ((offs + alignment - 1) & -alignment) - offs;
for (int i = 0; i < padding; ++i) {
bytes.addByte(0);
}
}
}
class BufferedReader {
int formatVersion;
StringReader stringReader;
ObjectReader objectReader;
final List<int> bytes;
final int baseOffset;
/// Position within [bytes], already includes [baseOffset].
int _pos;
BufferedReader(
this.formatVersion, this.stringReader, this.objectReader, this.bytes,
{this.baseOffset: 0})
: _pos = baseOffset {
assert((0 <= _pos) && (_pos <= bytes.length));
}
int get offset => _pos - baseOffset;
set offset(int offs) {
_pos = baseOffset + offs;
assert((0 <= _pos) && (_pos <= bytes.length));
}
int readByte() => bytes[_pos++];
int readUInt32() {
return (readByte() << 24) |
(readByte() << 16) |
(readByte() << 8) |
readByte();
}
int readPackedUInt30() {
var byte = readByte();
if (byte & 0x80 == 0) {
// 0xxxxxxx
return byte;
} else if (byte & 0x40 == 0) {
// 10xxxxxx
return ((byte & 0x3F) << 8) | readByte();
} else {
// 11xxxxxx
return ((byte & 0x3F) << 24) |
(readByte() << 16) |
(readByte() << 8) |
readByte();
}
}
int readSLEB128() {
int value = 0;
int shift = 0;
int part = 0;
do {
part = readByte();
value |= (part & 0x7f) << shift;
shift += 7;
} while ((part & 0x80) != 0);
const int kBitsPerInt = 64;
if ((shift < kBitsPerInt) && ((part & 0x40) != 0)) {
value |= (-1) << shift;
}
return value;
}
String readPackedStringReference() {
return stringReader.get(readPackedUInt30());
}
BytecodeObject readPackedObject() {
return objectReader.readObject(this);
}
List<T> readPackedList<T extends BytecodeObject>() {
final int len = readPackedUInt30();
final list = new List<T>(len);
for (int i = 0; i < len; ++i) {
list[i] = readPackedObject();
}
return list;
}
Uint8List readBytesAsUint8List(int count) {
final Uint8List result = new Uint8List(count);
result.setRange(0, result.length, bytes, _pos);
_pos += count;
return result;
}
Uint16List readBytesAsUint16List(int count) {
final Uint16List result = new Uint16List(count);
int pos = _pos;
for (int i = 0; i < count; ++i) {
result[i] = bytes[pos] | (bytes[pos + 1] << 8);
pos += 2;
}
_pos += count << 1;
return result;
}
void align(int alignment) {
assert(alignment & (alignment - 1) == 0);
_pos = ((_pos + alignment - 1) & -alignment);
}
}
class StringTable implements StringWriter, StringReader {
// Bit 0 in string reference is set for two-byte strings.
static const int flagTwoByteString = 1;
Map<String, int> _map = <String, int>{};
List<String> _oneByteStrings = <String>[];
List<String> _twoByteStrings = <String>[];
bool _written = false;
StringTable();
@override
int put(String string) {
int ref = _map[string];
if (ref == null) {
if (_written) {
throw 'Unable to add a string to string table after it was written';
}
if (isOneByteString(string)) {
ref = (_oneByteStrings.length << 1);
_oneByteStrings.add(string);
} else {
ref = (_twoByteStrings.length << 1) | flagTwoByteString;
_twoByteStrings.add(string);
}
_map[string] = ref;
}
return ref;
}
@override
String get(int ref) {
if ((ref & flagTwoByteString) == 0) {
return _oneByteStrings[ref >> 1];
} else {
return _twoByteStrings[ref >> 1];
}
}
bool isOneByteString(String value) {
const int maxLatin1 = 0xff;
for (int i = 0; i < value.length; ++i) {
if (value.codeUnitAt(i) > maxLatin1) {
return false;
}
}
return true;
}
void write(BufferedWriter writer) {
writer.writeUInt32(_oneByteStrings.length);
writer.writeUInt32(_twoByteStrings.length);
int endOffset = 0;
for (var str in _oneByteStrings) {
endOffset += str.length;
writer.writeUInt32(endOffset);
}
for (var str in _twoByteStrings) {
endOffset += str.length << 1;
writer.writeUInt32(endOffset);
}
for (var str in _oneByteStrings) {
for (int i = 0; i < str.length; ++i) {
writer.writeByte(str.codeUnitAt(i));
}
}
for (var str in _twoByteStrings) {
for (int i = 0; i < str.length; ++i) {
int utf16codeUnit = str.codeUnitAt(i);
writer.writeByte(utf16codeUnit & 0xFF);
writer.writeByte(utf16codeUnit >> 8);
}
}
_written = true;
}
StringTable.read(BufferedReader reader) {
final int numOneByteStrings = reader.readUInt32();
final int numTwoByteStrings = reader.readUInt32();
final List<int> oneByteEndOffsets = new List<int>(numOneByteStrings);
for (int i = 0; i < oneByteEndOffsets.length; ++i) {
oneByteEndOffsets[i] = reader.readUInt32();
}
List<int> twoByteEndOffsets = new List<int>(numTwoByteStrings);
for (int i = 0; i < twoByteEndOffsets.length; ++i) {
twoByteEndOffsets[i] = reader.readUInt32();
}
int start = 0;
if (numOneByteStrings > 0) {
_oneByteStrings = new List<String>(numOneByteStrings);
final charCodes = reader.readBytesAsUint8List(oneByteEndOffsets.last);
for (int i = 0; i < _oneByteStrings.length; ++i) {
final end = oneByteEndOffsets[i];
final str = new String.fromCharCodes(charCodes, start, end);
_oneByteStrings[i] = str;
_map[str] = i << 1;
start = end;
}
}
final int twoByteBaseOffset = start;
if (numTwoByteStrings > 0) {
int start = 0;
_twoByteStrings = new List<String>(numTwoByteStrings);
final charCodes = reader.readBytesAsUint16List(
(twoByteEndOffsets.last - twoByteBaseOffset) >> 1);
for (int i = 0; i < _twoByteStrings.length; ++i) {
final end = (twoByteEndOffsets[i] - twoByteBaseOffset) >> 1;
final str = new String.fromCharCodes(charCodes, start, end);
_twoByteStrings[i] = str;
_map[str] = (i << 1) | flagTwoByteString;
start = end;
}
}
}
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.writeln('StringTable {');
sb.writeln(' // One Byte Strings');
for (String str in _oneByteStrings) {
sb.writeln(' "$str"');
}
sb.writeln(' // Two Byte Strings');
for (String str in _twoByteStrings) {
sb.writeln(' "$str"');
}
sb.writeln('}');
return sb.toString();
}
}