improved findLineForOffset() performance using binary search and Map access

This commit is contained in:
Steven Hugg committed 2026-09-10 13:43:45 +02:00
1 parent df93d0a706
commit 9b8e831cb4
4 files changed
+35 -27

No files matched your search

+22 -13
View File
@@ -33,6 +33,7 @@ export class SourceFile {
text: string;
offset2loc: Map<number,SourceLine>; //{[offset:number]:number};
line2offset: Map<number,number>; //{[line:number]:number};
sortedOffsets: number[];
constructor(lines:SourceLine[], text:string) {
lines = lines || [];
@@ -44,25 +45,33 @@ export class SourceFile {
if (info.offset >= 0) {
// first line wins (is assigned to offset)
// TODO: handle macros/includes w/ multiple offsets per line
if (!this.offset2loc[info.offset])
this.offset2loc[info.offset] = info;
if (!this.line2offset[info.line])
this.line2offset[info.line] = info.offset;
if (!this.offset2loc.has(info.offset))
this.offset2loc.set(info.offset, info);
if (!this.line2offset.has(info.line))
this.line2offset.set(info.line, info.offset);
}
}
this.sortedOffsets = Array.from(this.offset2loc.keys()).sort((a,b)=>a-b);
}
// TODO: smarter about looking for source lines between two addresses
// returns the line whose offset is nearest to (but not greater than) PC,
// provided it is within `lookbehind` bytes; null otherwise
findLineForOffset(PC:number, lookbehind:number) : SourceLine {
if (this.offset2loc) {
for (var i=0; i<=lookbehind; i++) {
var loc = this.offset2loc[PC];
if (loc) {
return loc;
}
PC--;
const offsets = this.sortedOffsets;
// binary search for last offset <= PC
var lo = 0, hi = offsets.length-1, ans = -1;
while (lo <= hi) {
var mid = (lo + hi) >> 1;
if (offsets[mid] <= PC) {
ans = mid;
lo = mid + 1;
} else {
hi = mid - 1;
}
}
return null;
if (ans < 0) return null;
var off = offsets[ans];
if (PC - off > lookbehind) return null;
return this.offset2loc.get(off);
}
lineCount():number { return this.lines.length; }
}
+1 -1
View File
@@ -175,7 +175,7 @@ export function resolveBreakpoint(bp: Breakpoint): ResolvedBreakpoint {
let lst = current_project.getListingForFile(bp.file);
let sf = lst && (lst.sourcefile || lst.assemblyfile);
if (!sf) return { bp, error: "no debug info (build first?)" };
pc = sf.line2offset[bp.line];
pc = sf.line2offset.get(bp.line);
if (!(pc >= 0)) return { bp, error: "line has no code" };
} else {
let r = parseTarget(bp.target, makeSymbolLookup());
+4 -5
View File
@@ -294,7 +294,7 @@ export class SourceEditor implements ProjectView {
if (effect.is(currentPcMarker.runToLine)) {
const lineNum = effect.value;
if (this.sourcefile && this.sourcefile.line2offset) {
const pc = this.sourcefile.line2offset[lineNum];
const pc = this.sourcefile.line2offset.get(lineNum);
if (pc >= 0) {
runToPC([pc]);
}
@@ -576,8 +576,7 @@ export class SourceEditor implements ProjectView {
setTimingResult(result: CodeAnalyzer): void {
if (this.sourcefile == null) return;
var newBytes = new Map<number, string>();
for (const line of Object.keys(this.sourcefile.line2offset)) {
let pc = this.sourcefile.line2offset[line];
for (const [line, pc] of this.sourcefile.line2offset) {
let clocks = result.pc2clockrange[pc];
var minclocks = clocks && clocks.minclocks;
var maxclocks = clocks && clocks.maxclocks;
@@ -589,7 +588,7 @@ export class SourceEditor implements ProjectView {
s = minclocks + "-" + maxclocks;
if (maxclocks == result.MAX_CLOCKS)
s += "+";
newBytes.set(parseInt(line), s);
newBytes.set(line, s);
}
}
this.editor.dispatch({
@@ -719,7 +718,7 @@ export class SourceEditor implements ProjectView {
getCursorPC(): number {
var line = this.getCurrentLine();
while (this.sourcefile && line >= 0) {
var pc = this.sourcefile.line2offset[line];
var pc = this.sourcefile.line2offset.get(line);
if (pc >= 0) return pc;
line--;
}
+8 -8
View File
@@ -113,9 +113,9 @@ describe('SourceFile Class', function () {
];
const sf = new SourceFile(lines, '');
assert.ok(sf.offset2loc[0]);
assert.ok(sf.offset2loc[2]);
assert.ok(sf.offset2loc[4]);
assert.ok(sf.offset2loc.get(0));
assert.ok(sf.offset2loc.get(2));
assert.ok(sf.offset2loc.get(4));
});
it('should build line to offset mapping', function () {
@@ -126,9 +126,9 @@ describe('SourceFile Class', function () {
];
const sf = new SourceFile(lines, '');
assert.strictEqual(sf.line2offset[1], 0);
assert.strictEqual(sf.line2offset[2], 2);
assert.strictEqual(sf.line2offset[3], 4);
assert.strictEqual(sf.line2offset.get(1), 0);
assert.strictEqual(sf.line2offset.get(2), 2);
assert.strictEqual(sf.line2offset.get(3), 4);
});
it('should handle negative offsets gracefully', function () {
@@ -140,8 +140,8 @@ describe('SourceFile Class', function () {
const sf = new SourceFile(lines, '');
// Negative offsets should not be added to mapping
assert.ok(!sf.offset2loc[-1]);
assert.ok(sf.offset2loc[0]);
assert.ok(!sf.offset2loc.has(-1));
assert.ok(sf.offset2loc.get(0));
});
it('findLineForOffset should locate source line by address', function () {