Skip to content

Commit 0366d56

Browse files
committed
Make export-module and reference maps invertible
Right now, we're enumerating all the entries to find out which keys map to a corresponding value. By maintaining a two-way map, we can convert this linear search into a map lookup and skip allocation of many, many iterator results.
1 parent 5fde871 commit 0366d56

File tree

3 files changed

+169
-78
lines changed

3 files changed

+169
-78
lines changed

src/compiler/builder.ts

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ namespace ts {
212212
const copyLibFileDiagnostics = copyDeclarationFileDiagnostics && !compilerOptions.skipDefaultLibCheck === !oldCompilerOptions!.skipDefaultLibCheck;
213213
state.fileInfos.forEach((info, sourceFilePath) => {
214214
let oldInfo: Readonly<BuilderState.FileInfo> | undefined;
215-
let newReferences: BuilderState.ReferencedSet | undefined;
215+
let newReferences: ReadonlySet<Path> | undefined;
216216

217217
// if not using old state, every file is changed
218218
if (!useOldState ||
@@ -311,7 +311,7 @@ namespace ts {
311311
newState.affectedFilesIndex = state.affectedFilesIndex;
312312
newState.currentChangedFilePath = state.currentChangedFilePath;
313313
newState.currentAffectedFilesSignatures = state.currentAffectedFilesSignatures && new Map(state.currentAffectedFilesSignatures);
314-
newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && new Map(state.currentAffectedFilesExportedModulesMap);
314+
newState.currentAffectedFilesExportedModulesMap = state.currentAffectedFilesExportedModulesMap && { exporting: state.currentAffectedFilesExportedModulesMap.exporting.clone(), nonExporting: new Set(state.currentAffectedFilesExportedModulesMap.nonExporting) };
315315
newState.seenAffectedFiles = state.seenAffectedFiles && new Set(state.seenAffectedFiles);
316316
newState.cleanedDiagnosticsOfLibFiles = state.cleanedDiagnosticsOfLibFiles;
317317
newState.semanticDiagnosticsFromOldState = state.semanticDiagnosticsFromOldState && new Set(state.semanticDiagnosticsFromOldState);
@@ -384,7 +384,12 @@ namespace ts {
384384
// Get next batch of affected files
385385
if (!state.currentAffectedFilesSignatures) state.currentAffectedFilesSignatures = new Map();
386386
if (state.exportedModulesMap) {
387-
if (!state.currentAffectedFilesExportedModulesMap) state.currentAffectedFilesExportedModulesMap = new Map();
387+
if (!state.currentAffectedFilesExportedModulesMap) {
388+
state.currentAffectedFilesExportedModulesMap = {
389+
exporting: BuilderState.createTwoWayMap<Path, Path>(),
390+
nonExporting: new Set<Path>(),
391+
};
392+
}
388393
}
389394
state.affectedFiles = BuilderState.getFilesAffectedBy(state, program, nextKey.value, cancellationToken, computeHash, state.currentAffectedFilesSignatures, state.currentAffectedFilesExportedModulesMap);
390395
state.currentChangedFilePath = nextKey.value;
@@ -465,7 +470,7 @@ namespace ts {
465470
* Handle the dts may change, so they need to be added to pending emit if dts emit is enabled,
466471
* Also we need to make sure signature is updated for these files
467472
*/
468-
function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash) {
473+
function handleDtsMayChangeOf(state: BuilderProgramState, path: Path, cancellationToken: CancellationToken | undefined, computeHash: BuilderState.ComputeHash): void {
469474
removeSemanticDiagnosticsOf(state, path);
470475

471476
if (!state.changedFilesSet.has(path)) {
@@ -491,8 +496,6 @@ namespace ts {
491496
}
492497
}
493498
}
494-
495-
return false;
496499
}
497500

498501
/**
@@ -517,7 +520,7 @@ namespace ts {
517520
/**
518521
* Iterate on referencing modules that export entities from affected file
519522
*/
520-
function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => boolean) {
523+
function forEachReferencingModulesOfExportOfAffectedFile(state: BuilderProgramState, affectedFile: SourceFile, fn: (state: BuilderProgramState, filePath: Path) => void) {
521524
// If there was change in signature (dts output) for the changed file,
522525
// then only we need to handle pending file emit
523526
if (!state.exportedModulesMap || !state.changedFilesSet.has(affectedFile.resolvedPath)) {
@@ -536,8 +539,8 @@ namespace ts {
536539
const currentPath = queue.pop()!;
537540
if (!seenFileNamesMap.has(currentPath)) {
538541
seenFileNamesMap.set(currentPath, true);
539-
const result = fn(state, currentPath);
540-
if (result && isChangedSignature(state, currentPath)) {
542+
fn(state, currentPath);
543+
if (isChangedSignature(state, currentPath)) {
541544
const currentSourceFile = Debug.checkDefined(state.program).getSourceFileByPath(currentPath)!;
542545
queue.push(...BuilderState.getReferencedByPaths(state, currentSourceFile.resolvedPath));
543546
}
@@ -546,70 +549,59 @@ namespace ts {
546549
}
547550

548551
Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
552+
549553
const seenFileAndExportsOfFile = new Set<string>();
550554
// Go through exported modules from cache first
551555
// If exported modules has path, all files referencing file exported from are affected
552-
if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) =>
553-
exportedModules &&
554-
exportedModules.has(affectedFile.resolvedPath) &&
556+
state.currentAffectedFilesExportedModulesMap.exporting.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath =>
555557
forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn)
556-
)) {
557-
return;
558-
}
558+
);
559559

560560
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
561-
forEachEntry(state.exportedModulesMap, (exportedModules, exportedFromPath) =>
562-
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
563-
exportedModules.has(affectedFile.resolvedPath) &&
561+
state.exportedModulesMap.getKeys(affectedFile.resolvedPath)?.forEach(exportedFromPath =>
562+
// If the cache had an updated value, skip
563+
!state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) &&
564+
!state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) &&
564565
forEachFilesReferencingPath(state, exportedFromPath, seenFileAndExportsOfFile, fn)
565566
);
566567
}
567568

568569
/**
569570
* Iterate on files referencing referencedPath
570571
*/
571-
function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean) {
572-
return forEachEntry(state.referencedMap!, (referencesInFile, filePath) =>
573-
referencesInFile.has(referencedPath) && forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn)
572+
function forEachFilesReferencingPath(state: BuilderProgramState, referencedPath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => void): void {
573+
state.referencedMap!.getKeys(referencedPath)?.forEach(filePath =>
574+
forEachFileAndExportsOfFile(state, filePath, seenFileAndExportsOfFile, fn)
574575
);
575576
}
576577

577578
/**
578579
* fn on file and iterate on anything that exports this file
579580
*/
580-
function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => boolean): boolean {
581+
function forEachFileAndExportsOfFile(state: BuilderProgramState, filePath: Path, seenFileAndExportsOfFile: Set<string>, fn: (state: BuilderProgramState, filePath: Path) => void): void {
581582
if (!tryAddToSet(seenFileAndExportsOfFile, filePath)) {
582-
return false;
583+
return;
583584
}
584585

585-
if (fn(state, filePath)) {
586-
// If there are no more diagnostics from old cache, done
587-
return true;
588-
}
586+
fn(state, filePath);
589587

590588
Debug.assert(!!state.currentAffectedFilesExportedModulesMap);
591589
// Go through exported modules from cache first
592590
// If exported modules has path, all files referencing file exported from are affected
593-
if (forEachEntry(state.currentAffectedFilesExportedModulesMap, (exportedModules, exportedFromPath) =>
594-
exportedModules &&
595-
exportedModules.has(filePath) &&
591+
state.currentAffectedFilesExportedModulesMap.exporting.getKeys(filePath)?.forEach(exportedFromPath =>
596592
forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn)
597-
)) {
598-
return true;
599-
}
593+
);
600594

601595
// If exported from path is not from cache and exported modules has path, all files referencing file exported from are affected
602-
if (forEachEntry(state.exportedModulesMap!, (exportedModules, exportedFromPath) =>
603-
!state.currentAffectedFilesExportedModulesMap!.has(exportedFromPath) && // If we already iterated this through cache, ignore it
604-
exportedModules.has(filePath) &&
596+
state.exportedModulesMap!.getKeys(filePath)?.forEach(exportedFromPath =>
597+
// If the cache had an updated value, skip
598+
!state.currentAffectedFilesExportedModulesMap!.exporting.has(exportedFromPath) &&
599+
!state.currentAffectedFilesExportedModulesMap!.nonExporting.has(exportedFromPath) &&
605600
forEachFileAndExportsOfFile(state, exportedFromPath, seenFileAndExportsOfFile, fn)
606-
)) {
607-
return true;
608-
}
601+
);
609602

610603
// Remove diagnostics of files that import this file (without going to exports of referencing files)
611-
return !!forEachEntry(state.referencedMap!, (referencesInFile, referencingFilePath) =>
612-
referencesInFile.has(filePath) &&
604+
state.referencedMap!.getKeys(filePath)?.forEach(referencingFilePath =>
613605
!seenFileAndExportsOfFile.has(referencingFilePath) && // Not already removed diagnostic file
614606
fn(state, referencingFilePath) // Dont add to seen since this is not yet done with the export removal
615607
);
@@ -771,11 +763,19 @@ namespace ts {
771763
let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined;
772764
if (state.exportedModulesMap) {
773765
exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => {
774-
const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key);
766+
if (state.currentAffectedFilesExportedModulesMap) {
767+
if (state.currentAffectedFilesExportedModulesMap.nonExporting.has(key)) {
768+
return undefined;
769+
}
770+
771+
const newValue = state.currentAffectedFilesExportedModulesMap.exporting.get(key);
772+
if (newValue) {
773+
return [toFileId(key), toFileIdListId(newValue)];
774+
}
775+
}
776+
775777
// Not in temporary cache, use existing value
776-
if (newValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)];
777-
// Value in cache and has updated value map, use that
778-
else if (newValue) return [toFileId(key), toFileIdListId(newValue)];
778+
return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)];
779779
});
780780
}
781781

@@ -1253,8 +1253,8 @@ namespace ts {
12531253
const state: ReusableBuilderProgramState = {
12541254
fileInfos,
12551255
compilerOptions: program.options ? convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath) : {},
1256-
referencedMap: toMapOfReferencedSet(program.referencedMap),
1257-
exportedModulesMap: toMapOfReferencedSet(program.exportedModulesMap),
1256+
referencedMap: toTwoWayMap(program.referencedMap),
1257+
exportedModulesMap: toTwoWayMap(program.exportedModulesMap),
12581258
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
12591259
hasReusableDiagnostic: true,
12601260
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
@@ -1302,8 +1302,16 @@ namespace ts {
13021302
return filePathsSetList![fileIdsListId - 1];
13031303
}
13041304

1305-
function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
1306-
return referenceMap && arrayToMap(referenceMap, value => toFilePath(value[0]), value => toFilePathsSet(value[1]));
1305+
function toTwoWayMap(referenceMap: ProgramBuildInfoReferencedMap | undefined): BuilderState.TwoWayMap<Path, Path> | undefined {
1306+
if (!referenceMap) {
1307+
return undefined;
1308+
}
1309+
1310+
const map = BuilderState.createTwoWayMap<Path, Path>();
1311+
referenceMap.forEach(([fileId, fileIdListId]) =>
1312+
map.set(toFilePath(fileId), toFilePathsSet(fileIdListId))
1313+
);
1314+
return map;
13071315
}
13081316
}
13091317

0 commit comments

Comments
 (0)