Skip to content

Commit

Permalink
optimize: use mutable data, unswitch loops
Browse files Browse the repository at this point in the history
  • Loading branch information
milahu committed Sep 25, 2020
1 parent e7abdfa commit 422cc0d
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 75 deletions.
17 changes: 7 additions & 10 deletions src/compiler/preprocess/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,21 @@ async function replace_async(
);
return '';
});
let out: StringWithSourcemap;
const out = new StringWithSourcemap();
let last_end = 0;
for (const { offset, length, replacement } of await Promise.all(
replacements
)) {
// content = source before replacement
const content = StringWithSourcemap.from_source(
filename, source.slice(last_end, offset), get_location(last_end));
out = out ? out.concat(content) : content;
out = out.concat(replacement);
out.concat(content).concat(replacement);
last_end = offset + length;
}
// final_content = source after last replacement
const final_content = StringWithSourcemap.from_source(
filename, source.slice(last_end), get_location(last_end));
out = out.concat(final_content);
return out;
return out.concat(final_content);
}

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

let processed_map_shifted;
let decoded_map;
if (processed.map) {
const decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
decoded_map = typeof processed.map === "string" ? JSON.parse(processed.map) : processed.map;
if (typeof(decoded_map.mappings) === 'string')
decoded_map.mappings = sourcemap_decode(decoded_map.mappings);
const processed_offset = get_location(offset + prefix.length);
processed_map_shifted = sourcemap_add_offset(decoded_map, processed_offset);
sourcemap_add_offset(decoded_map, get_location(offset + prefix.length));
}
const processed_with_map = StringWithSourcemap.from_processed(processed.code, processed_map_shifted);
const processed_with_map = StringWithSourcemap.from_processed(processed.code, decoded_map);

return prefix_with_map.concat(processed_with_map).concat(suffix_with_map);
}
Expand Down
157 changes: 92 additions & 65 deletions src/compiler/utils/string_with_sourcemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type MappingSegment =
| [number, number, number, number, number];

type SourceMappings = {
version: number;
sources: string[];
names: string[];
mappings: MappingSegment[][];
Expand All @@ -18,30 +19,28 @@ function last_line_length(s: string) {
return s.length - s.lastIndexOf('\n') - 1;
}

// mutate map in-place
export function sourcemap_add_offset(
map: SourceMappings, offset: SourceLocation
): SourceMappings {
return {
sources: map.sources.slice(),
mappings: map.mappings.map((line, line_idx) =>
line.map(seg => {
const new_seg = seg.slice() as MappingSegment;
if (seg.length >= 4) {
new_seg[2] = new_seg[2] + offset.line;
if (line_idx == 0)
new_seg[3] = new_seg[3] + offset.column;
}
return new_seg;
})
)
} as SourceMappings;
) {
// shift columns in first line
const m = map.mappings as any;
m[0].forEach(seg => {
if (seg[3]) seg[3] += offset.column;
});
// shift lines
m.forEach(line => {
line.forEach(seg => {
if (seg[2]) seg[2] += offset.line;
});
});
}

function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean] {
function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean, boolean] {
const new_table = this_table.slice();
const idx_map = [];
other_table = other_table || [];
let has_changed = false;
let val_changed = false;
for (const [other_idx, other_val] of other_table.entries()) {
const this_idx = this_table.indexOf(other_val);
if (this_idx >= 0) {
Expand All @@ -50,47 +49,85 @@ function merge_tables<T>(this_table: T[], other_table): [T[], number[], boolean]
const new_idx = new_table.length;
new_table[new_idx] = other_val;
idx_map[other_idx] = new_idx;
has_changed = true;
val_changed = true;
}
}
if (has_changed) {
let idx_changed = val_changed;
if (val_changed) {
if (idx_map.find((val, idx) => val != idx) === undefined) {
// idx_map is identity map [0, 1, 2, 3, 4, ....]
has_changed = false;
idx_changed = false;
}
}
return [new_table, idx_map, has_changed];
return [new_table, idx_map, val_changed, idx_changed];
}

function pushArray<T>(_this: T[], other: T[]) {
for (let i = 0; i < other.length; i++)
_this.push(other[i]);
}

export class StringWithSourcemap {
readonly string: string;
readonly map: SourceMappings;
string: string;
map: SourceMappings;

constructor(string: string, map: SourceMappings) {
constructor(string = '', map = null) {
this.string = string;
this.map = map;
if (map)
this.map = map as SourceMappings;
else
this.map = {
version: 3,
mappings: [],
sources: [],
names: []
};
}

// concat in-place (mutable), return this (chainable)
// will also mutate the `other` object
concat(other: StringWithSourcemap): StringWithSourcemap {
// noop: if one is empty, return the other
if (this.string == '') return other;
if (other.string == '') return this;
if (this.string == '') {
this.string = other.string;
this.map = other.map;
return this;
}

this.string += other.string;

const m1 = this.map as any;
const m2 = other.map as any;

// combine sources and names
const [sources, new_source_idx, sources_changed] = merge_tables(this.map.sources, other.map.sources);
const [names, new_name_idx, names_changed] = merge_tables(this.map.names, other.map.names);

// update source refs and name refs
const other_mappings =
(sources_changed || names_changed)
? other.map.mappings.slice().map(line =>
line.map(seg => {
if (seg[1]) seg[1] = new_source_idx[seg[1]];
if (seg[4]) seg[4] = new_name_idx[seg[4]];
return seg;
})
)
: other.map.mappings;
const [sources, new_source_idx, sources_changed, sources_idx_changed] = merge_tables(m1.sources, m2.sources);
const [names, new_name_idx, names_changed, names_idx_changed] = merge_tables(m1.names, m2.names);

if (sources_changed) m1.sources = sources;
if (names_changed) m1.names = names;

// unswitched loops are faster
if (sources_idx_changed && names_idx_changed) {
m2.forEach(line => {
line.forEach(seg => {
if (seg[1]) seg[1] = new_source_idx[seg[1]];
if (seg[4]) seg[4] = new_name_idx[seg[4]];
});
});
} else if (sources_idx_changed) {
m2.forEach(line => {
line.forEach(seg => {
if (seg[1]) seg[1] = new_source_idx[seg[1]];
});
});
} else if (names_idx_changed) {
m2.forEach(line => {
line.forEach(seg => {
if (seg[4]) seg[4] = new_name_idx[seg[4]];
});
});
}

// combine the mappings

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

const column_offset = last_line_length(this.string);
if (m2.length > 0 && column_offset > 0) {
// shift columns in first line
m2[0].forEach(seg => {
seg[0] += column_offset;
});
}

// combine last line + first line
pushArray(m1.mappings[m1.mappings.length - 1], m2.mappings.shift());

// append other lines
pushArray(m1.mappings, m2.mappings);

const first_line: MappingSegment[] =
other_mappings.length == 0
? []
: column_offset == 0
? other_mappings[0].slice() as MappingSegment[]
: other_mappings[0].slice().map(seg => {
// shift column
seg[0] += column_offset;
return seg;
});

const mappings: MappingSegment[][] =
this.map.mappings.slice(0, -1)
.concat([
this.map.mappings.slice(-1)[0] // last line
.concat(first_line)
])
.concat(other_mappings.slice(1) as MappingSegment[][]);

return new StringWithSourcemap(
this.string + other.string,
{ sources, names, mappings }
);
return this;
}

static from_processed(string: string, map?: SourceMappings): StringWithSourcemap {
if (map) return new StringWithSourcemap(string, map);
map = { names: [], sources: [], mappings: [] };
map = { version: 3, names: [], sources: [], mappings: [] };
if (string == '') return new StringWithSourcemap(string, map);
// add empty MappingSegment[] for every line
const lineCount = string.split('\n').length;
Expand All @@ -140,7 +167,7 @@ export class StringWithSourcemap {
source_file: string, source: string, offset_in_source?: SourceLocation
): StringWithSourcemap {
const offset = offset_in_source || { line: 0, column: 0 };
const map: SourceMappings = { names: [], sources: [source_file], mappings: [] };
const map: SourceMappings = { version: 3, names: [], sources: [source_file], mappings: [] };
if (source.length == 0) return new StringWithSourcemap(source, map);

// we create a high resolution identity map here,
Expand Down

0 comments on commit 422cc0d

Please sign in to comment.