372b02d97f
Change-Id: I22bbd3b7ea0cccc0a8d721c3766a80ae1c72060d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/87701 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Régis Crelier <regis@google.com> Reviewed-by: Ryan Macnak <rmacnak@google.com>
510 lines
13 KiB
Dart
510 lines
13 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 ByteData, Endian, 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 LinkWriter linkWriter;
|
|
final BytesBuilder bytes = new BytesBuilder();
|
|
final int baseOffset;
|
|
|
|
BufferedWriter(
|
|
this.formatVersion, this.stringWriter, this.objectWriter, this.linkWriter,
|
|
{this.baseOffset: 0});
|
|
|
|
factory BufferedWriter.fromWriter(BufferedWriter writer) =>
|
|
new BufferedWriter(writer.formatVersion, writer.stringWriter,
|
|
writer.objectWriter, writer.linkWriter);
|
|
|
|
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 writeLinkOffset(Object target) {
|
|
final offset = linkWriter.getOffset(target);
|
|
writePackedUInt30(offset);
|
|
}
|
|
|
|
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;
|
|
LinkReader linkReader;
|
|
final List<int> bytes;
|
|
final int baseOffset;
|
|
|
|
/// Position within [bytes], already includes [baseOffset].
|
|
int _pos;
|
|
|
|
BufferedReader(this.formatVersion, this.stringReader, this.objectReader,
|
|
this.linkReader, 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;
|
|
}
|
|
|
|
T readLinkOffset<T>() {
|
|
final offset = readPackedUInt30();
|
|
return linkReader.get<T>(offset);
|
|
}
|
|
|
|
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) {
|
|
final start = writer.offset;
|
|
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;
|
|
BytecodeSizeStatistics.stringTableSize += (writer.offset - start);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
int doubleToIntBits(double value) {
|
|
final buf = new ByteData(8);
|
|
buf.setFloat64(0, value, Endian.little);
|
|
return buf.getInt64(0, Endian.little);
|
|
}
|
|
|
|
double intBitsToDouble(int bits) {
|
|
final buf = new ByteData(8);
|
|
buf.setInt64(0, bits, Endian.little);
|
|
return buf.getFloat64(0, Endian.little);
|
|
}
|
|
|
|
class PackedUInt30DeltaEncoder {
|
|
int _last = 0;
|
|
|
|
void write(BufferedWriter write, int value) {
|
|
write.writePackedUInt30(value - _last);
|
|
_last = value;
|
|
}
|
|
}
|
|
|
|
class PackedUInt30DeltaDecoder {
|
|
int _last = 0;
|
|
|
|
int read(BufferedReader reader) {
|
|
int value = reader.readPackedUInt30() + _last;
|
|
_last = value;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
class SLEB128DeltaEncoder {
|
|
int _last = 0;
|
|
|
|
void write(BufferedWriter writer, int value) {
|
|
writer.writeSLEB128(value - _last);
|
|
_last = value;
|
|
}
|
|
}
|
|
|
|
class SLEB128DeltaDecoder {
|
|
int _last = 0;
|
|
|
|
int read(BufferedReader reader) {
|
|
int value = reader.readSLEB128() + _last;
|
|
_last = value;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
class LinkWriter {
|
|
final _map = <Object, int>{};
|
|
|
|
void put(Object target, int offset) {
|
|
_map[target] = offset;
|
|
}
|
|
|
|
int getOffset(Object target) {
|
|
return _map[target] ??
|
|
(throw 'Offset of ${target.runtimeType} $target is not set');
|
|
}
|
|
}
|
|
|
|
class LinkReader {
|
|
final _map = <Type, Map<int, Object>>{};
|
|
|
|
void setOffset<T>(T target, int offset) {
|
|
final offsetToObject = (_map[T] ??= <int, Object>{});
|
|
final previous = offsetToObject[offset];
|
|
if (previous != null) {
|
|
throw 'Unable to associate offset $T/$offset with ${target.runtimeType} $target.'
|
|
' It is already associated with ${previous.runtimeType} $previous';
|
|
}
|
|
offsetToObject[offset] = target;
|
|
}
|
|
|
|
T get<T>(int offset) {
|
|
return _map[T][offset] ?? (throw 'No object at offset $T/$offset');
|
|
}
|
|
}
|
|
|
|
class NamedEntryStatistics {
|
|
final String name;
|
|
int size = 0;
|
|
int count = 0;
|
|
|
|
NamedEntryStatistics(this.name);
|
|
|
|
String toString() => "${name.padRight(40)}: ${size.toString().padLeft(10)}"
|
|
" (count: ${count.toString().padLeft(8)})";
|
|
}
|
|
|
|
class BytecodeSizeStatistics {
|
|
static int componentSize = 0;
|
|
static int objectTableSize = 0;
|
|
static int objectTableEntriesCount = 0;
|
|
static int stringTableSize = 0;
|
|
static int membersSize = 0;
|
|
static int constantPoolSize = 0;
|
|
static int instructionsSize = 0;
|
|
static List<NamedEntryStatistics> constantPoolStats =
|
|
<NamedEntryStatistics>[];
|
|
static List<NamedEntryStatistics> objectTableStats = <NamedEntryStatistics>[];
|
|
|
|
static void reset() {
|
|
componentSize = 0;
|
|
objectTableSize = 0;
|
|
objectTableEntriesCount = 0;
|
|
stringTableSize = 0;
|
|
membersSize = 0;
|
|
constantPoolSize = 0;
|
|
instructionsSize = 0;
|
|
constantPoolStats = <NamedEntryStatistics>[];
|
|
objectTableStats = <NamedEntryStatistics>[];
|
|
}
|
|
|
|
static void dump() {
|
|
print("Bytecode size statistics:");
|
|
print(" Bytecode component: $componentSize");
|
|
print(
|
|
" - object table: $objectTableSize (count: $objectTableEntriesCount)");
|
|
for (var entry in objectTableStats) {
|
|
print(" - $entry");
|
|
}
|
|
print(" - string table: $stringTableSize");
|
|
print(" Bytecode members: $membersSize");
|
|
print(" - constant pool: $constantPoolSize");
|
|
for (var entry in constantPoolStats) {
|
|
print(" - $entry");
|
|
}
|
|
print(" - instructions: $instructionsSize");
|
|
}
|
|
}
|