diff --git a/src/common/workertypes.ts b/src/common/workertypes.ts index 63e6f532..eca034a9 100644 --- a/src/common/workertypes.ts +++ b/src/common/workertypes.ts @@ -33,6 +33,7 @@ export class SourceFile { text: string; offset2loc: Map; //{[offset:number]:number}; line2offset: Map; //{[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; } } diff --git a/src/ide/breakpoints.ts b/src/ide/breakpoints.ts index 29845ac2..7011d0a7 100644 --- a/src/ide/breakpoints.ts +++ b/src/ide/breakpoints.ts @@ -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()); diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts index 670e862f..31f070ea 100644 --- a/src/ide/views/editors.ts +++ b/src/ide/views/editors.ts @@ -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(); - 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--; } diff --git a/test/unit/testcommon_ext.ts b/test/unit/testcommon_ext.ts index ec3186e8..77af0259 100644 --- a/test/unit/testcommon_ext.ts +++ b/test/unit/testcommon_ext.ts @@ -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 () {