Skip to content

Commit 422cc0d

Browse files
committed
optimize: use mutable data, unswitch loops
1 parent e7abdfa commit 422cc0d

File tree

2 files changed

+99
-75
lines changed

2 files changed

+99
-75
lines changed

src/compiler/preprocess/index.ts

+7-10
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,21 @@ async function replace_async(
6767
);
6868
return '';
6969
});
70-
let out: StringWithSourcemap;
70+
const out = new StringWithSourcemap();
7171
let last_end = 0;
7272
for (const { offset, length, replacement } of await Promise.all(
7373
replacements
7474
)) {
7575
// content = source before replacement
7676
const content = StringWithSourcemap.from_source(
7777
filename, source.slice(last_end, offset), get_location(last_end));
78-
out = out ? out.concat(content) : content;
79-
out = out.concat(replacement);
78+
out.concat(content).concat(replacement);
8079
last_end = offset + length;
8180
}
8281
// final_content = source after last replacement
8382
const final_content = StringWithSourcemap.from_source(
8483
filename, source.slice(last_end), get_location(last_end));
85-
out = out.concat(final_content);
86-
return out;
84+
return out.concat(final_content);
8785
}
8886

8987
function get_replacement(
@@ -100,15 +98,14 @@ function get_replacement(
10098
const suffix_with_map = StringWithSourcemap.from_source(
10199
filename, suffix, get_location(offset + prefix.length + original.length));
102100

103-
let processed_map_shifted;
101+
let decoded_map;
104102
if (processed.map) {
105-
const decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
103+
decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
106104
if (typeof(decoded_map.mappings) === 'string')
107105
decoded_map.mappings = sourcemap_decode(decoded_map.mappings);
108-
const processed_offset = get_location(offset + prefix.length);
109-
processed_map_shifted = sourcemap_add_offset(decoded_map, processed_offset);
106+
sourcemap_add_offset(decoded_map, get_location(offset + prefix.length));
110107
}
111-
const processed_with_map = StringWithSourcemap.from_processed(processed.code, processed_map_shifted);
108+
const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);
112109

113110
return prefix_with_map.concat(processed_with_map).concat(suffix_with_map);
114111
}

src/compiler/utils/string_with_sourcemap.ts

+92-65
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ type MappingSegment =
44
| [number, number, number, number, number];
55

66
type SourceMappings = {
7+
version: number;
78
sources: string[];
89
names: string[];
910
mappings: MappingSegment[][];
@@ -18,30 +19,28 @@ function last_line_length(s: string) {
1819
return s.length - s.lastIndexOf('\n') - 1;
1920
}
2021

22+
// mutate map in-place
2123
export function sourcemap_add_offset(
2224
map: SourceMappings, offset: SourceLocation
23-
): SourceMappings {
24-
return {
25-
sources: map.sources.slice(),
26-
mappings: map.mappings.map((line, line_idx) =>
27-
line.map(seg => {
28-
const new_seg = seg.slice() as MappingSegment;
29-
if (seg.length >= 4) {
30-
new_seg[2] = new_seg[2] + offset.line;
31-
if (line_idx == 0)
32-
new_seg[3] = new_seg[3] + offset.column;
33-
}
34-
return new_seg;
35-
})
36-
)
37-
} as SourceMappings;
25+
) {
26+
// shift columns in first line
27+
const m = map.mappings as any;
28+
m[0].forEach(seg => {
29+
if (seg[3]) seg[3] += offset.column;
30+
});
31+
// shift lines
32+
m.forEach(line => {
33+
line.forEach(seg => {
34+
if (seg[2]) seg[2] += offset.line;
35+
});
36+
});
3837
}
3938

40-
function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean] {
39+
function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean, boolean] {
4140
const new_table = this_table.slice();
4241
const idx_map = [];
4342
other_table = other_table || [];
44-
let has_changed = false;
43+
let val_changed = false;
4544
for (const [other_idx, other_val] of other_table.entries()) {
4645
const this_idx = this_table.indexOf(other_val);
4746
if (this_idx >= 0) {
@@ -50,47 +49,85 @@ function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean]
5049
const new_idx = new_table.length;
5150
new_table[new_idx] = other_val;
5251
idx_map[other_idx] = new_idx;
53-
has_changed = true;
52+
val_changed = true;
5453
}
5554
}
56-
if (has_changed) {
55+
let idx_changed = val_changed;
56+
if (val_changed) {
5757
if (idx_map.find((val, idx) => val != idx) === undefined) {
5858
// idx_map is identity map [0, 1, 2, 3, 4, ....]
59-
has_changed = false;
59+
idx_changed = false;
6060
}
6161
}
62-
return [new_table, idx_map, has_changed];
62+
return [new_table, idx_map, val_changed, idx_changed];
63+
}
64+
65+
function pushArray<T>(_this: T[], other: T[]) {
66+
for (let i = 0; i < other.length; i++)
67+
_this.push(other[i]);
6368
}
6469

6570
export class StringWithSourcemap {
66-
readonly string: string;
67-
readonly map: SourceMappings;
71+
string: string;
72+
map: SourceMappings;
6873

69-
constructor(string: string, map: SourceMappings) {
74+
constructor(string = '', map = null) {
7075
this.string = string;
71-
this.map = map;
76+
if (map)
77+
this.map = map as SourceMappings;
78+
else
79+
this.map = {
80+
version: 3,
81+
mappings: [],
82+
sources: [],
83+
names: []
84+
};
7285
}
7386

87+
// concat in-place (mutable), return this (chainable)
88+
// will also mutate the `other` object
7489
concat(other: StringWithSourcemap): StringWithSourcemap {
7590
// noop: if one is empty, return the other
76-
if (this.string == '') return other;
7791
if (other.string == '') return this;
92+
if (this.string == '') {
93+
this.string = other.string;
94+
this.map = other.map;
95+
return this;
96+
}
97+
98+
this.string += other.string;
99+
100+
const m1 = this.map as any;
101+
const m2 = other.map as any;
78102

79103
// combine sources and names
80-
const [sources, new_source_idx, sources_changed] = merge_tables(this.map.sources, other.map.sources);
81-
const [names, new_name_idx, names_changed] = merge_tables(this.map.names, other.map.names);
82-
83-
// update source refs and name refs
84-
const other_mappings =
85-
(sources_changed || names_changed)
86-
? other.map.mappings.slice().map(line =>
87-
line.map(seg => {
88-
if (seg[1]) seg[1] = new_source_idx[seg[1]];
89-
if (seg[4]) seg[4] = new_name_idx[seg[4]];
90-
return seg;
91-
})
92-
)
93-
: other.map.mappings;
104+
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
105+
const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names);
106+
107+
if (sources_changed) m1.sources = sources;
108+
if (names_changed) m1.names = names;
109+
110+
// unswitched loops are faster
111+
if (sources_idx_changed && names_idx_changed) {
112+
m2.forEach(line => {
113+
line.forEach(seg => {
114+
if (seg[1]) seg[1] = new_source_idx[seg[1]];
115+
if (seg[4]) seg[4] = new_name_idx[seg[4]];
116+
});
117+
});
118+
} else if (sources_idx_changed) {
119+
m2.forEach(line => {
120+
line.forEach(seg => {
121+
if (seg[1]) seg[1] = new_source_idx[seg[1]];
122+
});
123+
});
124+
} else if (names_idx_changed) {
125+
m2.forEach(line => {
126+
line.forEach(seg => {
127+
if (seg[4]) seg[4] = new_name_idx[seg[4]];
128+
});
129+
});
130+
}
94131

95132
// combine the mappings
96133

@@ -100,35 +137,25 @@ export class StringWithSourcemap {
100137
// columns of 2 must be shifted
101138

102139
const column_offset = last_line_length(this.string);
140+
if (m2.length > 0 && column_offset > 0) {
141+
// shift columns in first line
142+
m2[0].forEach(seg => {
143+
seg[0] += column_offset;
144+
});
145+
}
146+
147+
// combine last line + first line
148+
pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());
149+
150+
// append other lines
151+
pushArray(m1.mappings, m2.mappings);
103152

104-
const first_line: MappingSegment[] =
105-
other_mappings.length == 0
106-
? []
107-
: column_offset == 0
108-
? other_mappings[0].slice() as MappingSegment[]
109-
: other_mappings[0].slice().map(seg => {
110-
// shift column
111-
seg[0] += column_offset;
112-
return seg;
113-
});
114-
115-
const mappings: MappingSegment[][] =
116-
this.map.mappings.slice(0, -1)
117-
.concat([
118-
this.map.mappings.slice(-1)[0] // last line
119-
.concat(first_line)
120-
])
121-
.concat(other_mappings.slice(1) as MappingSegment[][]);
122-
123-
return new StringWithSourcemap(
124-
this.string + other.string,
125-
{ sources, names, mappings }
126-
);
153+
return this;
127154
}
128155

129156
static from_processed(string: string, map?: SourceMappings): StringWithSourcemap {
130157
if (map) return new StringWithSourcemap(string, map);
131-
map = { names: [], sources: [], mappings: [] };
158+
map = { version: 3, names: [], sources: [], mappings: [] };
132159
if (string == '') return new StringWithSourcemap(string, map);
133160
// add empty MappingSegment[] for every line
134161
const lineCount = string.split('\n').length;
@@ -140,7 +167,7 @@ export class StringWithSourcemap {
140167
source_file: string, source: string, offset_in_source?: SourceLocation
141168
): StringWithSourcemap {
142169
const offset = offset_in_source || { line: 0, column: 0 };
143-
const map: SourceMappings = { names: [], sources: [source_file], mappings: [] };
170+
const map: SourceMappings = { version: 3, names: [], sources: [source_file], mappings: [] };
144171
if (source.length == 0) return new StringWithSourcemap(source, map);
145172

146173
// we create a high resolution identity map here,

0 commit comments

Comments
 (0)