Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 71 additions & 49 deletions packages/trace-mapping/benchmark/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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`);

Expand Down Expand Up @@ -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;
});
}
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -388,15 +398,13 @@ 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);
currentGeneratedPositionFor(decoded, {
source: firstSource,
line: 6,
column: 0,
bias: 1,
});
})
.add('trace-mapping: encoded generatedPositionFor', () => {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
});
}
Expand All @@ -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
Expand All @@ -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;
Expand Down
52 changes: 14 additions & 38 deletions packages/trace-mapping/src/by-source.ts
Original file line number Diff line number Diff line change
@@ -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<ReverseSegment, [number]>[];
};
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];
Expand All @@ -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<T>(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 extends { __proto__: null }>(): T {
return { __proto__: null } as T;
return sources;
}
4 changes: 2 additions & 2 deletions packages/trace-mapping/src/sort.ts
Original file line number Diff line number Diff line change
@@ -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[][],
Expand Down Expand Up @@ -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<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number {
return a[COLUMN] - b[COLUMN];
}
8 changes: 3 additions & 5 deletions packages/trace-mapping/src/trace-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down