diff --git a/packages/trace-mapping/benchmark/index.mjs b/packages/trace-mapping/benchmark/index.mjs index c92d86a..bd1f056 100644 --- a/packages/trace-mapping/benchmark/index.mjs +++ b/packages/trace-mapping/benchmark/index.mjs @@ -21,7 +21,7 @@ import { SourceMapConsumer as SourceMapConsumerWasm } from 'source-map-wasm'; import { SourceMap as ChromeMap } from './chrome.mjs'; const dir = relative(process.cwd(), dirname(fileURLToPath(import.meta.url))); -const diff = !!process.env.DIFF; +const { DIFF, FILE } = process.env; console.log(`node ${process.version}\n`); @@ -76,43 +76,53 @@ async function bench(file) { currentDecoded = await track('trace-mapping decoded', results, () => { const decoded = new CurrentTraceMap(decodedMapData); currentTraceSegment(decoded, 0, 0); + currentGeneratedPositionFor(decoded, { source: decoded.sources[0], line: 1, column: 0 }); return decoded; }); + const firstSource = currentDecoded.sources[0]; currentEncoded = await track('trace-mapping encoded', results, () => { const encoded = new CurrentTraceMap(encodedMapData); currentTraceSegment(encoded, 0, 0); + currentGeneratedPositionFor(encoded, { source: firstSource, line: 1, column: 0 }); return encoded; }); - if (diff) { + if (DIFF) { latestDecoded = await track('trace-mapping latest decoded', results, () => { const decoded = new LatestTraceMap(decodedMapData); latestTraceSegment(decoded, 0, 0); + latestGeneratedPositionFor(decoded, { source: firstSource, line: 1, column: 0 }); return decoded; }); latestEncoded = await track('trace-mapping latest encoded', results, () => { const encoded = new LatestTraceMap(encodedMapData); latestTraceSegment(encoded, 0, 0); + latestGeneratedPositionFor(encoded, { source: firstSource, line: 1, column: 0 }); return encoded; }); } else { smcjs = await track('source-map-js', results, () => { const smcjs = new SourceMapConsumerJs(encodedMapData); smcjs.originalPositionFor({ line: 1, column: 0 }); + smcjs.generatedPositionFor({ source: firstSource, line: 1, column: 0 }); return smcjs; }); smc061 = await track('source-map-0.6.1', results, () => { const smc061 = new SourceMapConsumer061(encodedMapData); smc061.originalPositionFor({ line: 1, column: 0 }); + smc061.generatedPositionFor({ source: firstSource, line: 1, column: 0 }); return smc061; }); smcWasm = await track('source-map-0.8.0', results, async () => { const smcWasm = await new SourceMapConsumerWasm(encodedMapData); smcWasm.originalPositionFor({ line: 1, column: 0 }); + smcWasm.generatedPositionFor({ source: firstSource, line: 1, column: 0 }); return smcWasm; }); chromeMap = await track('Chrome dev tools', results, async () => { const map = new ChromeMap('url', encodedMapData); map.findEntry(0, 0); + const firstSource = map.sources()[0]; + map.findEntryReversed(firstSource, 6); return map; }); } @@ -138,7 +148,7 @@ async function bench(file) { .add('trace-mapping: encoded Object input', () => { currentTraceSegment(new CurrentTraceMap(encodedMapData), 0, 0); }); - if (diff) { + if (DIFF) { benchmark = benchmark .add('trace-mapping latest: decoded JSON input', () => { latestTraceSegment(new LatestTraceMap(decodedMapDataJson), 0, 0); @@ -203,7 +213,7 @@ async function bench(file) { currentTraceSegment(currentEncoded, i, column); } }); - if (diff) { + if (DIFF) { benchmark = benchmark .add('trace-mapping latest: decoded originalPositionFor', () => { const i = Math.floor(Math.random() * lines.length); @@ -309,7 +319,7 @@ async function bench(file) { currentTraceSegment(currentEncoded, i, column); } }); - if (diff) { + if (DIFF) { benchmark = benchmark .add('trace-mapping latest: decoded originalPositionFor', () => { const i = Math.floor(Math.random() * lines.length); @@ -388,7 +398,6 @@ async function bench(file) { console.log(''); console.log('Generated Positions init:'); - const firstSource = currentDecoded.sources[0]; benchmark = new Benchmark.Suite() .add('trace-mapping: decoded generatedPositionFor', () => { const decoded = new CurrentTraceMap(decodedMapData); @@ -396,7 +405,6 @@ async function bench(file) { source: firstSource, line: 6, column: 0, - bias: 1, }); }) .add('trace-mapping: encoded generatedPositionFor', () => { @@ -405,10 +413,9 @@ async function bench(file) { source: firstSource, line: 6, column: 0, - bias: 1, }); }); - if (diff) { + if (DIFF) { benchmark = benchmark .add('trace-mapping latest: decoded generatedPositionFor', () => { const decoded = new LatestTraceMap(decodedMapData); @@ -454,6 +461,7 @@ async function bench(file) { }) .add('Chrome dev tools: encoded generatedPositionFor', () => { const chromeMap = new ChromeMap('url', encodedMapData); + const firstSource = chromeMap.sources()[0]; chromeMap.findEntryReversed(firstSource, 6); }); } @@ -473,62 +481,76 @@ async function bench(file) { console.log('Generated Positions speed:'); benchmark = new Benchmark.Suite() .add('trace-mapping: decoded generatedPositionFor', () => { - currentGeneratedPositionFor(currentDecoded, { - source: firstSource, - line: 6, - column: 0, - bias: 1, - }); + for (const source of currentDecoded.sources) { + currentGeneratedPositionFor(currentDecoded, { + source, + line: 6, + column: 0, + }); + } }) .add('trace-mapping: encoded generatedPositionFor', () => { - currentGeneratedPositionFor(currentEncoded, { - source: firstSource, - line: 6, - column: 0, - bias: 1, - }); - }); - if (diff) { - benchmark = benchmark - .add('trace-mapping latest: decoded generatedPositionFor', () => { - latestGeneratedPositionFor(latestDecoded, { - source: firstSource, + for (const source of currentEncoded.sources) { + currentGeneratedPositionFor(currentEncoded, { + source, line: 6, column: 0, }); + } + }); + if (DIFF) { + benchmark = benchmark + .add('trace-mapping latest: decoded generatedPositionFor', () => { + for (const source of latestDecoded.sources) { + latestGeneratedPositionFor(latestDecoded, { + source, + line: 6, + column: 0, + }); + } }) .add('trace-mapping latest: encoded generatedPositionFor', () => { - latestGeneratedPositionFor(latestEncoded, { - source: firstSource, - line: 6, - column: 0, - }); + for (const source of latestEncoded.sources) { + latestGeneratedPositionFor(latestEncoded, { + source, + line: 6, + column: 0, + }); + } }); } else { benchmark = benchmark .add('source-map-js: encoded generatedPositionFor', () => { - smcjs.generatedPositionFor({ - source: firstSource, - line: 6, - column: 0, - }); + for (const source of smcjs.sources) { + smcjs.generatedPositionFor({ + source, + line: 6, + column: 0, + }); + } }) .add('source-map-0.6.1: encoded generatedPositionFor', () => { - smc061.generatedPositionFor({ - source: firstSource, - line: 6, - column: 0, - }); + for (const source of smc061.sources) { + smc061.generatedPositionFor({ + source, + line: 6, + column: 0, + }); + } }) .add('source-map-0.8.0: encoded generatedPositionFor', () => { - smcWasm.generatedPositionFor({ - source: firstSource, - line: 6, - column: 0, - }); + for (const source of smcWasm.sources) { + smcWasm.generatedPositionFor({ + source, + line: 6, + column: 0, + }); + } }) .add('Chrome dev tools: encoded generatedPositionFor', () => { - chromeMap.findEntryReversed(firstSource, 6); + for (const source of chromeMap.sources()) { + chromeMap.findEntryReversed(source, 6); + } }); } // add listeners @@ -549,7 +571,7 @@ async function run(files) { let first = true; for (const file of files) { if (!file.endsWith('.map')) continue; - if (file !== 'issue-41.js.map') continue; + if (FILE && file !== FILE) continue; if (!first) console.log('\n\n***\n\n'); first = false; diff --git a/packages/trace-mapping/src/by-source.ts b/packages/trace-mapping/src/by-source.ts index 2af1cf0..1da6af0 100644 --- a/packages/trace-mapping/src/by-source.ts +++ b/packages/trace-mapping/src/by-source.ts @@ -1,21 +1,17 @@ import { COLUMN, SOURCES_INDEX, SOURCE_LINE, SOURCE_COLUMN } from './sourcemap-segment'; -import { memoizedBinarySearch, upperBound } from './binary-search'; +import { sortComparator } from './sort'; import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment'; -import type { MemoState } from './binary-search'; -export type Source = { - __proto__: null; - [line: number]: Exclude[]; -}; +export type Source = ReverseSegment[][]; // Rebuilds the original source files, with mappings that are ordered by source line/column instead // of generated line/column. export default function buildBySources( decoded: readonly SourceMapSegment[][], - memos: MemoState[], + memos: unknown[], ): Source[] { - const sources: Source[] = memos.map(buildNullArray); + const sources: Source[] = memos.map(() => []); for (let i = 0; i < decoded.length; i++) { const line = decoded[i]; @@ -26,40 +22,20 @@ export default function buildBySources( const sourceIndex = seg[SOURCES_INDEX]; const sourceLine = seg[SOURCE_LINE]; const sourceColumn = seg[SOURCE_COLUMN]; - const originalSource = sources[sourceIndex]; - const originalLine = (originalSource[sourceLine] ||= []); - const memo = memos[sourceIndex]; - // The binary search either found a match, or it found the left-index just before where the - // segment should go. Either way, we want to insert after that. And there may be multiple - // generated segments associated with an original location, so there may need to move several - // indexes before we find where we need to insert. - let index = upperBound( - originalLine, - sourceColumn, - memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine), - ); - - memo.lastIndex = ++index; - insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]); + const source = sources[sourceIndex]; + const segs = (source[sourceLine] ||= []); + segs.push([sourceColumn, i, seg[COLUMN]]); } } - return sources; -} - -function insert(array: T[], index: number, value: T) { - for (let i = array.length; i > index; i--) { - array[i] = array[i - 1]; + for (let i = 0; i < sources.length; i++) { + const source = sources[i]; + for (let j = 0; j < source.length; j++) { + const line = source[j]; + if (line) line.sort(sortComparator); + } } - array[index] = value; -} -// Null arrays allow us to use ordered index keys without actually allocating contiguous memory like -// a real array. We use a null-prototype object to avoid prototype pollution and deoptimizations. -// Numeric properties on objects are magically sorted in ascending order by the engine regardless of -// the insertion order. So, by setting any numeric keys, even out of order, we'll get ascending -// order when iterating with for-in. -function buildNullArray(): T { - return { __proto__: null } as T; + return sources; } diff --git a/packages/trace-mapping/src/sort.ts b/packages/trace-mapping/src/sort.ts index 61213c8..5d016cb 100644 --- a/packages/trace-mapping/src/sort.ts +++ b/packages/trace-mapping/src/sort.ts @@ -1,6 +1,6 @@ import { COLUMN } from './sourcemap-segment'; -import type { SourceMapSegment } from './sourcemap-segment'; +import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment'; export default function maybeSort( mappings: SourceMapSegment[][], @@ -40,6 +40,6 @@ function sortSegments(line: SourceMapSegment[], owned: boolean): SourceMapSegmen return line.sort(sortComparator); } -function sortComparator(a: SourceMapSegment, b: SourceMapSegment): number { +export function sortComparator(a: T, b: T): number { return a[COLUMN] - b[COLUMN]; } diff --git a/packages/trace-mapping/src/trace-mapping.ts b/packages/trace-mapping/src/trace-mapping.ts index dea4c6c..0b793d5 100644 --- a/packages/trace-mapping/src/trace-mapping.ts +++ b/packages/trace-mapping/src/trace-mapping.ts @@ -484,15 +484,13 @@ function generatedPosition( if (sourceIndex === -1) sourceIndex = resolvedSources.indexOf(source); if (sourceIndex === -1) return all ? [] : GMapping(null, null); - const generated = (cast(map)._bySources ||= buildBySources( - decodedMappings(map), - (cast(map)._bySourceMemos = sources.map(memoizedState)), - )); + const bySourceMemos = (cast(map)._bySourceMemos ||= sources.map(memoizedState)); + const generated = (cast(map)._bySources ||= buildBySources(decodedMappings(map), bySourceMemos)); const segments = generated[sourceIndex][line]; if (segments == null) return all ? [] : GMapping(null, null); - const memo = cast(map)._bySourceMemos![sourceIndex]; + const memo = bySourceMemos[sourceIndex]; if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);