blob: e5aa6ed10db1893b77f33680732db1721d373311 [file] [log] [blame] [edit]
// 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);
}
// Maps the given PC to the given file offset. If there is already an entry
// for the given PC, then the new information overwrites the old information.
//
// 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.
void 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.
return;
}
if (lastPc == pc) {
// The new entry for this PC overwrites the old one.
_positions.removeRange(i, i + 2);
}
}
_positions.add(pc);
_positions.add(encodedFileOffset);
}
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';
}