Skip to content

Commit c261f19

Browse files
committed
lazy compute shapes and exported modules for better performance
1 parent e234f0c commit c261f19

File tree

172 files changed

+1392
-784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

172 files changed

+1392
-784
lines changed

src/compiler/builder.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace ts {
4141
* Map of file signatures, with key being file path, calculated while getting current changed file's affected files
4242
* These will be committed whenever the iteration through affected files of current changed file is complete
4343
*/
44-
currentAffectedFilesSignatures?: ReadonlyESMap<Path, string> | undefined;
44+
currentAffectedFilesSignatures?: ReadonlyESMap<Path, string | typeof NOT_COMPUTED_YET> | undefined;
4545
/**
4646
* Newly computed visible to outside referencedSet
4747
*/
@@ -110,7 +110,7 @@ namespace ts {
110110
* Map of file signatures, with key being file path, calculated while getting current changed file's affected files
111111
* These will be committed whenever the iteration through affected files of current changed file is complete
112112
*/
113-
currentAffectedFilesSignatures: ESMap<Path, string> | undefined;
113+
currentAffectedFilesSignatures: ESMap<Path, string | typeof NOT_COMPUTED_YET> | undefined;
114114
/**
115115
* Newly computed visible to outside referencedSet
116116
*/
@@ -496,7 +496,7 @@ namespace ts {
496496
function isChangedSignature(state: BuilderProgramState, path: Path) {
497497
const newSignature = Debug.checkDefined(state.currentAffectedFilesSignatures).get(path);
498498
const oldSignature = Debug.checkDefined(state.fileInfos.get(path)).signature;
499-
return newSignature !== oldSignature;
499+
return oldSignature === NOT_COMPUTED_YET || newSignature !== oldSignature;
500500
}
501501

502502
/**

src/compiler/builderState.ts

+32-14
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,16 @@ namespace ts {
6161
allFileNames?: readonly string[];
6262
}
6363

64+
export const NOT_COMPUTED_YET = 0;
65+
export const SHAPE_CHANGE_UNKNOWN = Symbol("shape change unknown");
66+
6467
export namespace BuilderState {
6568
/**
6669
* Information about the source file: Its version and optional signature from last emit
6770
*/
6871
export interface FileInfo {
6972
readonly version: string;
70-
signature: string | undefined;
73+
signature: string | typeof NOT_COMPUTED_YET | undefined;
7174
affectsGlobalScope: boolean;
7275
}
7376
/**
@@ -264,7 +267,7 @@ namespace ts {
264267
/**
265268
* Gets the files affected by the path from the program
266269
*/
267-
export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap<Path, string>, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] {
270+
export function getFilesAffectedBy(state: BuilderState, programOfThisState: Program, path: Path, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, cacheToUpdateSignature?: ESMap<Path, string | typeof NOT_COMPUTED_YET>, exportedModulesMapCache?: ComputingExportedModulesMap): readonly SourceFile[] {
268271
// Since the operation could be cancelled, the signatures are always stored in the cache
269272
// They will be committed once it is safe to use them
270273
// eg when calling this api from tsserver, if there is no cancellation of the operation
@@ -275,11 +278,12 @@ namespace ts {
275278
return emptyArray;
276279
}
277280

278-
if (!updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache)) {
281+
const updateResult = updateShapeSignature(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache, /*fromChange*/ true);
282+
if (!updateResult) {
279283
return [sourceFile];
280284
}
281285

282-
const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache);
286+
const result = (state.referencedMap ? getFilesAffectedByUpdatedShapeWhenModuleEmit : getFilesAffectedByUpdatedShapeWhenNonModuleEmit)(state, programOfThisState, sourceFile, signatureCache, cancellationToken, computeHash, exportedModulesMapCache, updateResult === true);
283287
if (!cacheToUpdateSignature) {
284288
// Commit all the signatures in the signature cache
285289
updateSignaturesFromCache(state, signatureCache);
@@ -291,19 +295,19 @@ namespace ts {
291295
* Updates the signatures from the cache into state's fileinfo signatures
292296
* This should be called whenever it is safe to commit the state of the builder
293297
*/
294-
export function updateSignaturesFromCache(state: BuilderState, signatureCache: ESMap<Path, string>) {
298+
export function updateSignaturesFromCache(state: BuilderState, signatureCache: ESMap<Path, string | typeof NOT_COMPUTED_YET>) {
295299
signatureCache.forEach((signature, path) => updateSignatureOfFile(state, signature, path));
296300
}
297301

298-
export function updateSignatureOfFile(state: BuilderState, signature: string | undefined, path: Path) {
302+
export function updateSignatureOfFile(state: BuilderState, signature: string | typeof NOT_COMPUTED_YET | undefined, path: Path) {
299303
state.fileInfos.get(path)!.signature = signature;
300304
state.hasCalledUpdateShapeSignature.add(path);
301305
}
302306

303307
/**
304308
* Returns if the shape of the signature has changed since last emit
305309
*/
306-
export function updateShapeSignature(state: Readonly<BuilderState>, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap<Path, string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap) {
310+
export function updateShapeSignature(state: Readonly<BuilderState>, programOfThisState: Program, sourceFile: SourceFile, cacheToUpdateSignature: ESMap<Path, string | typeof NOT_COMPUTED_YET>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache?: ComputingExportedModulesMap, fromChange?: boolean): (boolean | typeof SHAPE_CHANGE_UNKNOWN) {
307311
Debug.assert(!!sourceFile);
308312
Debug.assert(!exportedModulesMapCache || !!state.exportedModulesMap, "Compute visible to outside map only if visibleToOutsideReferencedMap present in the state");
309313

@@ -316,7 +320,7 @@ namespace ts {
316320
if (!info) return Debug.fail();
317321

318322
const prevSignature = info.signature;
319-
let latestSignature: string;
323+
let latestSignature: string | typeof NOT_COMPUTED_YET;
320324
if (sourceFile.isDeclarationFile) {
321325
latestSignature = sourceFile.version;
322326
if (exportedModulesMapCache && latestSignature !== prevSignature) {
@@ -326,6 +330,14 @@ namespace ts {
326330
}
327331
}
328332
else {
333+
if ((prevSignature === undefined || prevSignature === NOT_COMPUTED_YET && !fromChange) && !programOfThisState.getCompilerOptions().disableLazyShapeComputation) {
334+
if (exportedModulesMapCache) {
335+
const references = state.referencedMap ? state.referencedMap.get(sourceFile.resolvedPath) : undefined;
336+
exportedModulesMapCache.set(sourceFile.resolvedPath, references || false);
337+
}
338+
cacheToUpdateSignature.set(sourceFile.resolvedPath, NOT_COMPUTED_YET);
339+
return SHAPE_CHANGE_UNKNOWN;
340+
}
329341
const emitOutput = getFileEmitOutput(
330342
programOfThisState,
331343
sourceFile,
@@ -352,7 +364,7 @@ namespace ts {
352364
}
353365
cacheToUpdateSignature.set(sourceFile.resolvedPath, latestSignature);
354366

355-
return !prevSignature || latestSignature !== prevSignature;
367+
return prevSignature ? latestSignature !== prevSignature : SHAPE_CHANGE_UNKNOWN;
356368
}
357369

358370
/**
@@ -524,7 +536,7 @@ namespace ts {
524536
/**
525537
* When program emits modular code, gets the files affected by the sourceFile whose shape has changed
526538
*/
527-
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap<Path, string>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ComputingExportedModulesMap | undefined) {
539+
function getFilesAffectedByUpdatedShapeWhenModuleEmit(state: BuilderState, programOfThisState: Program, sourceFileWithUpdatedShape: SourceFile, cacheToUpdateSignature: ESMap<Path, string | typeof NOT_COMPUTED_YET>, cancellationToken: CancellationToken | undefined, computeHash: ComputeHash, exportedModulesMapCache: ComputingExportedModulesMap | undefined, realShapeChange: boolean) {
528540
if (isFileAffectingGlobalScope(sourceFileWithUpdatedShape)) {
529541
return getAllFilesExcludingDefaultLibraryFile(state, programOfThisState, sourceFileWithUpdatedShape);
530542
}
@@ -541,14 +553,20 @@ namespace ts {
541553

542554
// Start with the paths this file was referenced by
543555
seenFileNamesMap.set(sourceFileWithUpdatedShape.resolvedPath, sourceFileWithUpdatedShape);
544-
const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath);
556+
const queue = getReferencedByPaths(state, sourceFileWithUpdatedShape.resolvedPath).map(path => ({ path, updatedShape: realShapeChange }));
545557
while (queue.length > 0) {
546-
const currentPath = queue.pop()!;
558+
const { path: currentPath, updatedShape } = queue.pop()!;
547559
if (!seenFileNamesMap.has(currentPath)) {
548560
const currentSourceFile = programOfThisState.getSourceFileByPath(currentPath)!;
549561
seenFileNamesMap.set(currentPath, currentSourceFile);
550-
if (currentSourceFile && updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash, exportedModulesMapCache)) {
551-
queue.push(...getReferencedByPaths(state, currentSourceFile.resolvedPath));
562+
if (currentSourceFile) {
563+
const updateResult = updateShapeSignature(state, programOfThisState, currentSourceFile, cacheToUpdateSignature, cancellationToken, computeHash, exportedModulesMapCache, updatedShape);
564+
if (updateResult) {
565+
const updatedShape = updateResult === true;
566+
getReferencedByPaths(state, currentSourceFile.resolvedPath).forEach(path => {
567+
queue.push({ path, updatedShape });
568+
});
569+
}
552570
}
553571
}
554572
}

src/compiler/commandLineParser.ts

+6
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,12 @@ namespace ts {
941941
category: Diagnostics.Advanced_Options,
942942
description: Diagnostics.Do_not_emit_declarations_for_code_that_has_an_internal_annotation,
943943
},
944+
{
945+
name: "disableLazyShapeComputation",
946+
type: "boolean",
947+
category: Diagnostics.Advanced_Options,
948+
description: Diagnostics.Disable_lazy_computation_of_module_shapes
949+
},
944950
{
945951
name: "disableSizeLimit",
946952
type: "boolean",

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -4757,6 +4757,10 @@
47574757
"category": "Error",
47584758
"code": 6238
47594759
},
4760+
"Disable lazy computation of module shapes.": {
4761+
"category": "Message",
4762+
"code": 6239
4763+
},
47604764

47614765
"Projects to reference": {
47624766
"category": "Message",

src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5840,6 +5840,7 @@ namespace ts {
58405840
declarationDir?: string;
58415841
/* @internal */ diagnostics?: boolean;
58425842
/* @internal */ extendedDiagnostics?: boolean;
5843+
/* @internal */ disableLazyShapeComputation?: boolean;
58435844
disableSizeLimit?: boolean;
58445845
disableSourceOfProjectReferenceRedirect?: boolean;
58455846
disableSolutionSearching?: boolean;

src/server/protocol.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3315,6 +3315,7 @@ namespace ts.server.protocol {
33153315
checkJs?: boolean;
33163316
declaration?: boolean;
33173317
declarationDir?: string;
3318+
disableLazyShapeComputation?: boolean;
33183319
disableSizeLimit?: boolean;
33193320
downlevelIteration?: boolean;
33203321
emitBOM?: boolean;

src/testRunner/unittests/tsbuild/helpers.ts

+42-6
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,10 @@ interface Symbol {
300300
}
301301
else if (incrementalBuildText !== cleanBuildText) {
302302
// Verify build info without affectedFilesPendingEmit
303-
const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
304-
const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
303+
const { buildInfo: incrementalBuildInfo, affectedFilesPendingEmit: incrementalBuildAffectedFilesPendingEmit, signatures: incrementalSignatures, exportedModulesMap: incrementalExportedModulesMap } =
304+
getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
305+
const { buildInfo: cleanBuildInfo, affectedFilesPendingEmit: incrementalAffectedFilesPendingEmit, signatures: cleanSignatures, exportedModulesMap: cleanExportedModulesMap } =
306+
getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
305307
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, descrepancyInClean, `TsBuild info text without affectedFilesPendingEmit ${subScenario}:: ${outputFile}::\nIncremental buildInfoText:: ${incrementalBuildText}\nClean buildInfoText:: ${cleanBuildText}`);
306308
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
307309
if (incrementalBuildAffectedFilesPendingEmit && descrepancyInClean === undefined) {
@@ -313,6 +315,24 @@ interface Symbol {
313315
expectedIndex++;
314316
});
315317
}
318+
if (incrementalSignatures && cleanSignatures) {
319+
for (let i = 0; i < incrementalSignatures.length; i++) {
320+
if (incrementalSignatures[i] === 0) cleanSignatures[i] = 0;
321+
}
322+
}
323+
assert.deepEqual(incrementalSignatures, cleanSignatures);
324+
if (incrementalExportedModulesMap && cleanExportedModulesMap) {
325+
const keys = Object.keys(incrementalExportedModulesMap);
326+
if (keys.length > 0) {
327+
assert.containsAllKeys(cleanExportedModulesMap, keys);
328+
for (const key of keys) {
329+
assert.includeMembers(cleanExportedModulesMap[key], incrementalExportedModulesMap[key]);
330+
}
331+
}
332+
}
333+
else {
334+
assert.deepEqual(incrementalExportedModulesMap, cleanExportedModulesMap);
335+
}
316336
}
317337
}
318338

@@ -338,20 +358,36 @@ interface Symbol {
338358
});
339359
}
340360

341-
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): { buildInfo: string | undefined; affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"]; } {
361+
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
362+
buildInfo: string | undefined;
363+
affectedFilesPendingEmit?: ProgramBuildInfo["affectedFilesPendingEmit"];
364+
signatures?: BuilderState.FileInfo["signature"][];
365+
exportedModulesMap?: MapLike<string[]>;
366+
} {
342367
const buildInfo = text ? getBuildInfo(text) : undefined;
343368
if (!buildInfo?.program) return { buildInfo: text };
344369
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
345-
const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, ...programRest }, ...rest } = buildInfo;
370+
const { program: { affectedFilesPendingEmit, options: { noEmit, ...optionsRest}, fileInfos, exportedModulesMap, ...programRest }, ...rest } = buildInfo;
371+
const signatures: BuilderState.FileInfo["signature"][] = [];
346372
return {
347373
buildInfo: getBuildInfoText({
348374
...rest,
349375
program: {
376+
fileInfos: Object.keys(fileInfos).reduce<MapLike<BuilderState.FileInfo>>((newFileInfos, key) => {
377+
const { signature, ...remainingFileInfo } = fileInfos[key];
378+
newFileInfos[key] = {
379+
signature: 0,
380+
...remainingFileInfo
381+
};
382+
return newFileInfos;
383+
}, {}),
350384
options: optionsRest,
351385
...programRest
352-
}
386+
},
353387
}),
354-
affectedFilesPendingEmit
388+
affectedFilesPendingEmit,
389+
signatures,
390+
exportedModulesMap
355391
};
356392
}
357393

src/testRunner/unittests/tscWatch/emitAndErrorUpdates.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ namespace ts.tscWatch {
7373
verifyEmitAndErrorUpdatesWorker({
7474
...input,
7575
subScenario: `assumeChangesOnlyAffectDirectDependencies/${input.subScenario}`,
76-
configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true })
76+
configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, disableLazyShapeComputation: true })
7777
});
7878

7979
verifyEmitAndErrorUpdatesWorker({
8080
...input,
8181
subScenario: `assumeChangesOnlyAffectDirectDependenciesAndD/${input.subScenario}`,
82-
configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, declaration: true })
82+
configFile: () => changeCompilerOptions(input, { assumeChangesOnlyAffectDirectDependencies: true, disableLazyShapeComputation: true, declaration: true })
8383
});
8484
}
8585

src/testRunner/unittests/tscWatch/incremental.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ namespace ts.tscWatch {
110110
};
111111
const config: File = {
112112
path: configFile.path,
113-
content: JSON.stringify({ compilerOptions: { incremental: true, module: "amd" } })
113+
content: JSON.stringify({ compilerOptions: { incremental: true, disableLazyShapeComputation: true, module: "amd" } })
114114
};
115115

116116
verifyIncrementalWatchEmit({
@@ -175,6 +175,7 @@ namespace ts.tscWatch {
175175

176176
assert.deepEqual(state.compilerOptions, {
177177
incremental: true,
178+
disableLazyShapeComputation: true,
178179
module: ModuleKind.AMD,
179180
configFilePath: config.path
180181
});

src/testRunner/unittests/tsserver/compileOnSave.ts

+1
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ namespace ts.projectSystem {
905905
compileOnSave: true,
906906
compilerOptions: {
907907
declaration,
908+
disableLazyShapeComputation: true,
908909
module: hasModule ? undefined : "none"
909910
},
910911
})

0 commit comments

Comments
 (0)