Skip to content

Commit a0fa63b

Browse files
petebacondarwinAndrewKushnir
authored andcommitted
perf(ngcc): link segment markers for faster traversal (#36027)
The merging algorithm needs to find, for a given segment, what the next segment in the source file is. This change modifies the `generatedSegment` properties in the mappings so that they have a link directly to the following segment. PR Close #36027
1 parent daa2a08 commit a0fa63b

File tree

4 files changed

+231
-212
lines changed

4 files changed

+231
-212
lines changed

packages/compiler-cli/ngcc/src/sourcemaps/segment_marker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
export interface SegmentMarker {
1717
readonly line: number;
1818
readonly column: number;
19+
next: SegmentMarker|undefined;
1920
}
2021

2122
/**
@@ -64,5 +65,5 @@ export function offsetSegment(
6465
line--;
6566
}
6667
const column = newPos - startOfLinePositions[line];
67-
return {line, column};
68+
return {line, column, next: undefined};
6869
}

packages/compiler-cli/ngcc/src/sourcemaps/source_file.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class SourceFile {
9090
*/
9191
private flattenMappings(): Mapping[] {
9292
const mappings = parseMappings(this.rawMap, this.sources);
93-
const originalSegmentsBySource = extractOriginalSegments(mappings);
93+
ensureOriginalSegmentLinks(mappings);
9494
const flattenedMappings: Mapping[] = [];
9595
for (let mappingIndex = 0; mappingIndex < mappings.length; mappingIndex++) {
9696
const aToBmapping = mappings[mappingIndex];
@@ -120,13 +120,8 @@ export class SourceFile {
120120
// For mapping [0,0] the incoming start and end are 0 and 2 (i.e. the range a, b, c)
121121
// For mapping [4,2] the incoming start and end are 2 and 5 (i.e. the range c, d, e, f)
122122
//
123-
124-
const originalSegments = originalSegmentsBySource.get(bSource) !;
125123
const incomingStart = aToBmapping.originalSegment;
126-
const incomingEndIndex = originalSegments.indexOf(incomingStart) + 1;
127-
const incomingEnd = incomingEndIndex < originalSegments.length ?
128-
originalSegments[incomingEndIndex] :
129-
undefined;
124+
const incomingEnd = incomingStart.next;
130125

131126
// The `outgoingStartIndex` and `outgoingEndIndex` are the indices of the range of mappings
132127
// that leave `b` that we are interested in merging with the aToBmapping.
@@ -330,12 +325,19 @@ export function parseMappings(
330325
}
331326
const generatedColumn = rawMapping[0];
332327
const name = rawMapping.length === 5 ? rawMap.names[rawMapping[4]] : undefined;
333-
const mapping: Mapping = {
334-
generatedSegment: {line: generatedLine, column: generatedColumn},
335-
originalSource,
336-
originalSegment: {line: rawMapping[2] !, column: rawMapping[3] !}, name
328+
const line = rawMapping[2] !;
329+
const column = rawMapping[3] !;
330+
const generatedSegment: SegmentMarker = {
331+
line: generatedLine,
332+
column: generatedColumn,
333+
next: undefined,
334+
};
335+
const originalSegment: SegmentMarker = {
336+
line,
337+
column,
338+
next: undefined,
337339
};
338-
mappings.push(mapping);
340+
mappings.push({name, generatedSegment, originalSegment, originalSource});
339341
}
340342
}
341343
}
@@ -364,6 +366,21 @@ export function extractOriginalSegments(mappings: Mapping[]): Map<SourceFile, Se
364366
return originalSegments;
365367
}
366368

369+
/**
370+
* Update the original segments of each of the given `mappings` to include a link to the next
371+
* segment in the source file.
372+
*
373+
* @param mappings the mappings whose segments should be updated
374+
*/
375+
export function ensureOriginalSegmentLinks(mappings: Mapping[]): void {
376+
const segmentsBySource = extractOriginalSegments(mappings);
377+
segmentsBySource.forEach(markers => {
378+
for (let i = 0; i < markers.length - 1; i++) {
379+
markers[i].next = markers[i + 1];
380+
}
381+
});
382+
}
383+
367384
export function computeStartOfLinePositions(str: string) {
368385
// The `1` is to indicate a newline character between the lines.
369386
// Note that in the actual contents there could be more than one character that indicates a

packages/compiler-cli/ngcc/test/sourcemaps/segment_marker_spec.ts

Lines changed: 63 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,122 +11,88 @@ import {computeStartOfLinePositions} from '../../src/sourcemaps/source_file';
1111
describe('SegmentMarker utils', () => {
1212
describe('compareSegments()', () => {
1313
it('should return 0 if the segments are the same', () => {
14-
expect(compareSegments({line: 0, column: 0}, {line: 0, column: 0})).toEqual(0);
15-
expect(compareSegments({line: 123, column: 0}, {line: 123, column: 0})).toEqual(0);
16-
expect(compareSegments({line: 0, column: 45}, {line: 0, column: 45})).toEqual(0);
17-
expect(compareSegments({line: 123, column: 45}, {line: 123, column: 45})).toEqual(0);
18-
});
19-
20-
it('should return a negative number if the first segment is before the second segment', () => {
21-
expect(compareSegments({line: 0, column: 0}, {line: 0, column: 45})).toBeLessThan(0);
22-
expect(compareSegments({line: 123, column: 0}, {line: 123, column: 45})).toBeLessThan(0);
23-
expect(compareSegments({line: 13, column: 45}, {line: 123, column: 45})).toBeLessThan(0);
24-
expect(compareSegments({line: 13, column: 45}, {line: 123, column: 9})).toBeLessThan(0);
25-
});
26-
27-
it('should return a positive number if the first segment is after the second segment', () => {
28-
expect(compareSegments({line: 0, column: 45}, {line: 0, column: 0})).toBeGreaterThan(0);
29-
expect(compareSegments({line: 123, column: 45}, {line: 123, column: 0})).toBeGreaterThan(0);
30-
expect(compareSegments({line: 123, column: 45}, {line: 13, column: 45})).toBeGreaterThan(0);
31-
expect(compareSegments({line: 123, column: 9}, {line: 13, column: 45})).toBeGreaterThan(0);
32-
});
33-
});
34-
35-
describe('segmentDiff()', () => {
36-
it('should return 0 if the segments are the same', () => {
37-
const startOfLinePositions =
38-
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
39-
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 0}))
14+
expect(compareSegments(
15+
{line: 0, column: 0, next: undefined}, {line: 0, column: 0, next: undefined}))
4016
.toEqual(0);
41-
expect(segmentDiff(startOfLinePositions, {line: 3, column: 0}, {line: 3, column: 0}))
17+
expect(compareSegments(
18+
{line: 123, column: 0, next: undefined}, {line: 123, column: 0, next: undefined}))
4219
.toEqual(0);
43-
expect(segmentDiff(startOfLinePositions, {line: 0, column: 5}, {line: 0, column: 5}))
20+
expect(compareSegments(
21+
{line: 0, column: 45, next: undefined}, {line: 0, column: 45, next: undefined}))
4422
.toEqual(0);
45-
expect(segmentDiff(startOfLinePositions, {line: 3, column: 5}, {line: 3, column: 5}))
23+
expect(
24+
compareSegments(
25+
{line: 123, column: 45, next: undefined}, {line: 123, column: 45, next: undefined}))
4626
.toEqual(0);
4727
});
4828

49-
it('should return the column difference if the markers are on the same line', () => {
50-
const startOfLinePositions =
51-
computeStartOfLinePositions('abcdef\nabcdefghj\nabcdefghijklm\nabcdef');
52-
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 0, column: 3}))
53-
.toEqual(3);
54-
expect(segmentDiff(startOfLinePositions, {line: 1, column: 1}, {line: 1, column: 5}))
55-
.toEqual(4);
56-
expect(segmentDiff(startOfLinePositions, {line: 2, column: 5}, {line: 2, column: 1}))
57-
.toEqual(-4);
58-
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 3, column: 0}))
59-
.toEqual(-3);
29+
it('should return a negative number if the first segment is before the second segment', () => {
30+
expect(compareSegments(
31+
{line: 0, column: 0, next: undefined}, {line: 0, column: 45, next: undefined}))
32+
.toBeLessThan(0);
33+
expect(compareSegments(
34+
{line: 123, column: 0, next: undefined}, {line: 123, column: 45, next: undefined}))
35+
.toBeLessThan(0);
36+
expect(compareSegments(
37+
{line: 13, column: 45, next: undefined}, {line: 123, column: 45, next: undefined}))
38+
.toBeLessThan(0);
39+
expect(compareSegments(
40+
{line: 13, column: 45, next: undefined}, {line: 123, column: 9, next: undefined}))
41+
.toBeLessThan(0);
6042
});
6143

62-
it('should return the number of actual characters difference (including newline markers) if not on the same line',
63-
() => {
64-
let startOfLinePositions: number[];
65-
66-
startOfLinePositions = computeStartOfLinePositions('A12345\nB123456789');
67-
expect(segmentDiff(startOfLinePositions, {line: 0, column: 0}, {line: 1, column: 0}))
68-
.toEqual(6 + 1);
69-
70-
startOfLinePositions = computeStartOfLinePositions('012A45\n01234B6789');
71-
expect(segmentDiff(startOfLinePositions, {line: 0, column: 3}, {line: 1, column: 5}))
72-
.toEqual(3 + 1 + 5);
73-
74-
startOfLinePositions =
75-
computeStartOfLinePositions('012345\n012345A789\n01234567\nB123456');
76-
expect(segmentDiff(startOfLinePositions, {line: 1, column: 6}, {line: 3, column: 0}))
77-
.toEqual(4 + 1 + 8 + 1 + 0);
78-
79-
startOfLinePositions =
80-
computeStartOfLinePositions('012345\nA123456789\n01234567\n012B456');
81-
expect(segmentDiff(startOfLinePositions, {line: 1, column: 0}, {line: 3, column: 3}))
82-
.toEqual(10 + 1 + 8 + 1 + 3);
83-
84-
startOfLinePositions =
85-
computeStartOfLinePositions('012345\nB123456789\nA1234567\n0123456');
86-
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 1, column: 0}))
87-
.toEqual(0 - 1 - 10 + 0);
88-
89-
startOfLinePositions =
90-
computeStartOfLinePositions('012345\n0123B56789\n01234567\n012A456');
91-
expect(segmentDiff(startOfLinePositions, {line: 3, column: 3}, {line: 1, column: 4}))
92-
.toEqual(-3 - 1 - 8 - 1 - 10 + 4);
93-
94-
startOfLinePositions =
95-
computeStartOfLinePositions('B12345\n0123456789\n0123A567\n0123456');
96-
expect(segmentDiff(startOfLinePositions, {line: 2, column: 4}, {line: 0, column: 0}))
97-
.toEqual(-4 - 1 - 10 - 1 - 6 + 0);
98-
99-
startOfLinePositions =
100-
computeStartOfLinePositions('0123B5\n0123456789\nA1234567\n0123456');
101-
expect(segmentDiff(startOfLinePositions, {line: 2, column: 0}, {line: 0, column: 4}))
102-
.toEqual(0 - 1 - 10 - 1 - 6 + 4);
103-
});
44+
it('should return a positive number if the first segment is after the second segment', () => {
45+
expect(compareSegments(
46+
{line: 0, column: 45, next: undefined}, {line: 0, column: 0, next: undefined}))
47+
.toBeGreaterThan(0);
48+
expect(compareSegments(
49+
{line: 123, column: 45, next: undefined}, {line: 123, column: 0, next: undefined}))
50+
.toBeGreaterThan(0);
51+
expect(compareSegments(
52+
{line: 123, column: 45, next: undefined}, {line: 13, column: 45, next: undefined}))
53+
.toBeGreaterThan(0);
54+
expect(compareSegments(
55+
{line: 123, column: 9, next: undefined}, {line: 13, column: 45, next: undefined}))
56+
.toBeGreaterThan(0);
57+
});
10458
});
10559

10660
describe('offsetSegment()', () => {
10761
it('should return an identical marker if offset is 0', () => {
10862
const startOfLinePositions =
109-
computeStartOfLinePositions('012345\n0123456789\r\n01234567\n0123456');
110-
const marker = {line: 2, column: 3};
63+
computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
64+
const marker = {line: 2, column: 3, next: undefined};
11165
expect(offsetSegment(startOfLinePositions, marker, 0)).toBe(marker);
11266
});
11367

11468
it('should return a new marker offset by the given chars', () => {
11569
const startOfLinePositions =
11670
computeStartOfLinePositions('012345\n0123456789\r\n012*4567\n0123456');
117-
const marker = {line: 2, column: 3};
118-
expect(offsetSegment(startOfLinePositions, marker, 1)).toEqual({line: 2, column: 4});
119-
expect(offsetSegment(startOfLinePositions, marker, 2)).toEqual({line: 2, column: 5});
120-
expect(offsetSegment(startOfLinePositions, marker, 4)).toEqual({line: 2, column: 7});
121-
expect(offsetSegment(startOfLinePositions, marker, 6)).toEqual({line: 3, column: 0});
122-
expect(offsetSegment(startOfLinePositions, marker, 8)).toEqual({line: 3, column: 2});
123-
expect(offsetSegment(startOfLinePositions, marker, 20)).toEqual({line: 3, column: 14});
124-
expect(offsetSegment(startOfLinePositions, marker, -1)).toEqual({line: 2, column: 2});
125-
expect(offsetSegment(startOfLinePositions, marker, -2)).toEqual({line: 2, column: 1});
126-
expect(offsetSegment(startOfLinePositions, marker, -3)).toEqual({line: 2, column: 0});
127-
expect(offsetSegment(startOfLinePositions, marker, -4)).toEqual({line: 1, column: 10});
128-
expect(offsetSegment(startOfLinePositions, marker, -6)).toEqual({line: 1, column: 8});
129-
expect(offsetSegment(startOfLinePositions, marker, -16)).toEqual({line: 0, column: 5});
71+
const marker = {line: 2, column: 3, next: undefined};
72+
expect(offsetSegment(startOfLinePositions, marker, 1))
73+
.toEqual({line: 2, column: 4, next: undefined});
74+
expect(offsetSegment(startOfLinePositions, marker, 2))
75+
.toEqual({line: 2, column: 5, next: undefined});
76+
expect(offsetSegment(startOfLinePositions, marker, 4))
77+
.toEqual({line: 2, column: 7, next: undefined});
78+
expect(offsetSegment(startOfLinePositions, marker, 6))
79+
.toEqual({line: 3, column: 0, next: undefined});
80+
expect(offsetSegment(startOfLinePositions, marker, 8))
81+
.toEqual({line: 3, column: 2, next: undefined});
82+
expect(offsetSegment(startOfLinePositions, marker, 20))
83+
.toEqual({line: 3, column: 14, next: undefined});
84+
expect(offsetSegment(startOfLinePositions, marker, -1))
85+
.toEqual({line: 2, column: 2, next: undefined});
86+
expect(offsetSegment(startOfLinePositions, marker, -2))
87+
.toEqual({line: 2, column: 1, next: undefined});
88+
expect(offsetSegment(startOfLinePositions, marker, -3))
89+
.toEqual({line: 2, column: 0, next: undefined});
90+
expect(offsetSegment(startOfLinePositions, marker, -4))
91+
.toEqual({line: 1, column: 10, next: undefined});
92+
expect(offsetSegment(startOfLinePositions, marker, -6))
93+
.toEqual({line: 1, column: 8, next: undefined});
94+
expect(offsetSegment(startOfLinePositions, marker, -16))
95+
.toEqual({line: 0, column: 5, next: undefined});
13096
});
13197
});
13298
});

0 commit comments

Comments
 (0)