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