| // Copyright (c) 2024, 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. | |
| import 'bytecode_serialization.dart' | |
| show | |
| BufferedWriter, | |
| BufferedReader, | |
| BytecodeDeclaration, | |
| PackedUInt30DeltaEncoder, | |
| PackedUInt30DeltaDecoder, | |
| SLEB128DeltaEncoder, | |
| SLEB128DeltaDecoder; | |
| /// Maintains mapping between bytecode instructions and source positions. | |
| class SourcePositions extends BytecodeDeclaration { | |
| // Special value of fileOffset which marks synthetic code without a source | |
| // position. | |
| static const noSourcePosition = -1; | |
| // The flags encoded into the low bits of the source position. | |
| static const syntheticFlag = 1 << 0; | |
| static const yieldPointFlag = 1 << 1; | |
| static const _numFlags = 2; | |
| static const _flagMask = (1 << _numFlags) - 1; | |
| final _positions = <int>[]; // Pairs (PC, fileOffset). | |
| // Stored separately just to make sure no call to add uses a smaller | |
| // PC offset than the previous call, even if the previous call didn't | |
| // add an entry to the list because the last entry covers it. | |
| int _lastPcAdded = 0; | |
| SourcePositions(); | |
| int _encode(int fileOffset, int flags) => | |
| (flags == 0 || fileOffset == noSourcePosition) | |
| ? fileOffset | |
| : -((fileOffset << _numFlags) | flags) - 1; | |
| (int, int) _decode(int encoded) { | |
| if (encoded >= 0 || encoded == noSourcePosition) { | |
| return (encoded, 0); | |
| } | |
| final value = -encoded - 1; | |
| return (value >> _numFlags, value & _flagMask); | |
| } | |
| // Adds a mapping from the PC to the given file offset as long as there's | |
| // no mapping for that PC already, otherwise no change is made. Returns | |
| // whether the requested mapping exists, which can be either because a new | |
| // mapping was created or the mapping already existed before the request. | |
| // | |
| // Marks the source position as synthetic (not to be used by the debugger | |
| // or coverage calculations) if [(flags & syntheticFlag) != 0]. | |
| // | |
| // Marks the pc as within a yield point if [(flags & yieldPointFlag) != 0]. | |
| // | |
| // Assumes that the pc is greater than or equal to the pc used in the most | |
| // recent call to add, if any. | |
| bool add(int pc, int fileOffset, int flags) { | |
| assert(fileOffset >= 0 || fileOffset == noSourcePosition); | |
| assert((flags & ~_flagMask) == 0); | |
| if (_lastPcAdded > pc) { | |
| throw ArgumentError('Attempt to add entry for $pc after $_lastPcAdded'); | |
| } | |
| _lastPcAdded = pc; | |
| final encodedFileOffset = _encode(fileOffset, flags); | |
| if (_positions.isNotEmpty) { | |
| final i = _positions.length - 2; | |
| final lastPc = _positions[i]; | |
| final lastFileOffset = _positions[i + 1]; | |
| if (lastFileOffset == encodedFileOffset) { | |
| // The last entry covers this PC offset as well, or this is a repeated | |
| // request for the same (pc, offset) mapping. | |
| return true; | |
| } | |
| if (lastPc == pc) { | |
| // There's already a mapping for (pc, lastFileOffset). | |
| return false; | |
| } | |
| } | |
| _positions.add(pc); | |
| _positions.add(encodedFileOffset); | |
| return true; | |
| } | |
| bool get isEmpty => _positions.isEmpty; | |
| bool get isNotEmpty => !isEmpty; | |
| void write(BufferedWriter writer) { | |
| final pairs = _positions.length ~/ 2; | |
| writer.writePackedUInt30(pairs); | |
| final encodePC = new PackedUInt30DeltaEncoder(); | |
| final encodeOffset = new SLEB128DeltaEncoder(); | |
| for (int i = 0; i < pairs; i++) { | |
| encodePC.write(writer, _positions[2 * i]); | |
| encodeOffset.write(writer, _positions[2 * i + 1]); | |
| } | |
| } | |
| SourcePositions.read(BufferedReader reader) { | |
| final int pairs = reader.readPackedUInt30(); | |
| final decodePC = new PackedUInt30DeltaDecoder(); | |
| final decodeOffset = new SLEB128DeltaDecoder(); | |
| for (int i = 0; i < pairs; i++) { | |
| _positions.add(decodePC.read(reader)); | |
| _positions.add(decodeOffset.read(reader)); | |
| } | |
| _lastPcAdded = _positions.isEmpty ? 0 : _positions[_positions.length - 2]; | |
| } | |
| @override | |
| String toString() => _positions.toString(); | |
| Map<int, String> getBytecodeAnnotations() { | |
| final map = <int, String>{}; | |
| for (int i = 0; i < _positions.length; i += 2) { | |
| final pc = _positions[i]; | |
| final (fileOffset, flags) = _decode(_positions[i + 1]); | |
| String annotation = ''; | |
| if ((flags & syntheticFlag) != 0) { | |
| annotation += 'synthetic '; | |
| } | |
| if ((flags & yieldPointFlag) != 0) { | |
| annotation += 'yield point @ '; | |
| } | |
| annotation += 'source position $fileOffset'; | |
| // There is at most one entry per PC offset. | |
| assert(map[pc] == null); | |
| map[pc] = annotation; | |
| } | |
| return map; | |
| } | |
| } | |
| /// Keeps file offsets of line starts. This information is used to | |
| /// decode source positions to line/column. | |
| class LineStarts extends BytecodeDeclaration { | |
| final List<int> lineStarts; | |
| LineStarts(this.lineStarts); | |
| void write(BufferedWriter writer) { | |
| writer.writePackedUInt30(lineStarts.length); | |
| final encodeLineStarts = new PackedUInt30DeltaEncoder(); | |
| for (int lineStart in lineStarts) { | |
| encodeLineStarts.write(writer, lineStart); | |
| } | |
| } | |
| factory LineStarts.read(BufferedReader reader) { | |
| final decodeLineStarts = new PackedUInt30DeltaDecoder(); | |
| final lineStarts = new List<int>.generate( | |
| reader.readPackedUInt30(), | |
| (_) => decodeLineStarts.read(reader), | |
| ); | |
| return new LineStarts(lineStarts); | |
| } | |
| @override | |
| String toString() => 'Line starts: $lineStarts'; | |
| } | |
| enum RecordedCoverageType { | |
| // Used for most types of coverage. | |
| regular, | |
| // Used when recording that a branch reached a particular target. | |
| branchTarget, | |
| } | |
| /// Keeps types and file offsets of coverage information recorded | |
| /// by RecordCoverage instructions. | |
| /// | |
| /// RecordCoverage instructions use indices into the list of types | |
| /// and file offsets collected during generation, and the bytecode reader | |
| /// generates an appropriate coverage array from it at load time. | |
| class RecordedCoverageArray extends BytecodeDeclaration { | |
| final _recordedCoverageMap = <(RecordedCoverageType, int), int>{}; | |
| final _recordedCoverageList = <(RecordedCoverageType, int)>[]; | |
| RecordedCoverageArray(); | |
| bool get isEmpty => _recordedCoverageList.isEmpty; | |
| bool get isNotEmpty => !isEmpty; | |
| // Adds the type and file offset to the list of types and file offsets | |
| // recorded for RecordCoverage instructions. Returns the index into | |
| // the list for use as the argument to the RecordCoverage instruction. | |
| int add(RecordedCoverageType type, int fileOffset) { | |
| final key = (type, fileOffset); | |
| int? index = _recordedCoverageMap[key]; | |
| if (index == null) { | |
| index = _recordedCoverageList.length; | |
| _recordedCoverageList.add(key); | |
| _recordedCoverageMap[key] = index; | |
| } | |
| return index; | |
| } | |
| void write(BufferedWriter writer) { | |
| writer.writePackedUInt30(_recordedCoverageList.length); | |
| final encodeFileOffsets = new SLEB128DeltaEncoder(); | |
| for (final (type, fileOffset) in _recordedCoverageList) { | |
| writer.writePackedUInt30(type.index); | |
| encodeFileOffsets.write(writer, fileOffset); | |
| } | |
| } | |
| RecordedCoverageArray.read(BufferedReader reader) { | |
| final decodeFileOffsets = new SLEB128DeltaDecoder(); | |
| final length = reader.readPackedUInt30(); | |
| for (int i = 0; i < length; i++) { | |
| final type = RecordedCoverageType.values[(reader.readPackedUInt30())]; | |
| final fileOffset = decodeFileOffsets.read(reader); | |
| final key = (type, fileOffset); | |
| _recordedCoverageList.add(key); | |
| _recordedCoverageMap[key] = i; | |
| } | |
| } | |
| @override | |
| String toString() => _recordedCoverageList.toString(); | |
| } |