tsc-ts 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/api/compiler/ast.d.ts +15 -3
- package/dist/api/compiler/ast.js +36 -15
- package/dist/api/compiler/checker-classes.js +2 -1
- package/dist/api/compiler/checker-contextual.d.ts +20 -0
- package/dist/api/compiler/checker-contextual.js +111 -1
- package/dist/api/compiler/checker-expressions.js +14 -1
- package/dist/api/compiler/checker-grammar.d.ts +39 -4
- package/dist/api/compiler/checker-grammar.js +239 -75
- package/dist/api/compiler/checker-heritage.js +20 -14
- package/dist/api/compiler/checker-jsx.js +2 -1
- package/dist/api/compiler/checker-modules.js +4 -2
- package/dist/api/compiler/checker-source-file.js +1 -1
- package/dist/api/compiler/checker-type-factory.js +70 -19
- package/dist/api/compiler/checker-type-nodes.d.ts +1 -1
- package/dist/api/compiler/checker-type-nodes.js +5 -5
- package/dist/api/compiler/checker-unused.js +15 -10
- package/dist/api/compiler/checker.d.ts +7 -1
- package/dist/api/compiler/checker.js +14 -4
- package/dist/api/compiler/compile-program.d.ts +5 -0
- package/dist/api/compiler/compile-program.js +8 -0
- package/dist/api/compiler/expr-record.d.ts +11 -0
- package/dist/api/compiler/expr-record.js +53 -0
- package/dist/api/compiler/file-loader.d.ts +5 -0
- package/dist/api/compiler/file-loader.js +7 -0
- package/dist/api/compiler/identifier-name.d.ts +9 -0
- package/dist/api/compiler/identifier-name.js +10 -0
- package/dist/api/compiler/jsdoc.js +4 -2
- package/dist/api/compiler/parser.d.ts +2 -1
- package/dist/api/compiler/parser.js +10 -8
- package/dist/api/compiler/printer/factory.js +3 -2
- package/dist/api/compiler/printer/printer.js +3 -2
- package/dist/api/compiler/program-checker.d.ts +18 -0
- package/dist/api/compiler/program-checker.js +27 -0
- package/dist/api/compiler/reparser.js +4 -3
- package/dist/api/compiler/scanner.d.ts +8 -0
- package/dist/api/compiler/scanner.js +58 -42
- package/dist/api/compiler/shared-results.d.ts +376 -143
- package/dist/api/compiler/shared-results.js +1550 -773
- package/dist/api/compiler/transformers/declarations.js +2 -1
- package/package.json +15 -10
- package/src/compiler/ast.ts +39 -15
- package/src/compiler/cgroup.ts +157 -0
- package/src/compiler/checker-classes.ts +2 -1
- package/src/compiler/checker-contextual.ts +104 -0
- package/src/compiler/checker-expressions.ts +16 -1
- package/src/compiler/checker-grammar.ts +247 -88
- package/src/compiler/checker-heritage.ts +31 -17
- package/src/compiler/checker-jsx.ts +2 -1
- package/src/compiler/checker-modules.ts +4 -2
- package/src/compiler/checker-source-file.ts +1 -1
- package/src/compiler/checker-type-factory.ts +67 -21
- package/src/compiler/checker-type-nodes.ts +5 -5
- package/src/compiler/checker-unused.ts +13 -8
- package/src/compiler/checker.ts +15 -4
- package/src/compiler/compile-program.ts +8 -0
- package/src/compiler/decoder.ts +2 -1
- package/src/compiler/doctor-report.ts +133 -0
- package/src/compiler/expr-record.ts +59 -0
- package/src/compiler/file-loader.ts +8 -0
- package/src/compiler/identifier-name.ts +13 -0
- package/src/compiler/jsdoc.ts +4 -2
- package/src/compiler/parallel-check.ts +364 -146
- package/src/compiler/parallel-emit.ts +3 -3
- package/src/compiler/parse-thread.ts +24 -2
- package/src/compiler/parser.ts +10 -8
- package/src/compiler/printer/factory.ts +3 -2
- package/src/compiler/printer/printer.ts +4 -2
- package/src/compiler/program-checker.ts +28 -0
- package/src/compiler/reparser.ts +7 -3
- package/src/compiler/run-config.ts +1383 -0
- package/src/compiler/run-record-read.ts +87 -0
- package/src/compiler/run-records.ts +575 -0
- package/src/compiler/scanner.ts +57 -41
- package/src/compiler/shared-results.ts +1626 -896
- package/src/compiler/thread-entry.ts +3 -1
- package/src/compiler/transformers/declarations.ts +2 -1
- package/src/compiler/tsc-build.ts +64 -6
- package/src/compiler/tsc-cli.ts +33 -4
- package/src/compiler/tsc-compile.ts +223 -9
- package/src/compiler/tsc-watch.ts +25 -4
|
@@ -50,8 +50,9 @@ import type { Program } from './program.js';
|
|
|
50
50
|
import type { SourceFile } from './ast.js';
|
|
51
51
|
import { lastSymbolIdValue } from './symbol.js';
|
|
52
52
|
import { shareMergedGlobals } from './checker-symbols.js';
|
|
53
|
+
import { availableCpus, cgroupMemoryLimit } from './cgroup.js';
|
|
53
54
|
import { readFileSync } from 'node:fs';
|
|
54
|
-
import {
|
|
55
|
+
import { totalmem } from 'node:os';
|
|
55
56
|
import {
|
|
56
57
|
forbiddenWords,
|
|
57
58
|
isForbidden,
|
|
@@ -59,10 +60,68 @@ import {
|
|
|
59
60
|
numberLogSize,
|
|
60
61
|
recordTableSize,
|
|
61
62
|
resultTableSize,
|
|
62
|
-
|
|
63
|
+
shareStatColumns,
|
|
64
|
+
shareStatExport,
|
|
65
|
+
shareStatPosted,
|
|
66
|
+
shareStatSaved,
|
|
67
|
+
shareStatTranslation,
|
|
68
|
+
shareStatTranslated,
|
|
63
69
|
stringLogSize,
|
|
70
|
+
usedStride,
|
|
64
71
|
type ThreadSharingData,
|
|
65
72
|
} from './shared-results.js';
|
|
73
|
+
import {
|
|
74
|
+
autoCheckers,
|
|
75
|
+
Automaticity,
|
|
76
|
+
CheckSignals,
|
|
77
|
+
checkersPolicy,
|
|
78
|
+
decideCheck,
|
|
79
|
+
learns,
|
|
80
|
+
libraryBoundCount,
|
|
81
|
+
maxAutoCheckers,
|
|
82
|
+
minSharingCheckers,
|
|
83
|
+
sharePin,
|
|
84
|
+
sharingCandidate,
|
|
85
|
+
sharingPeakShare,
|
|
86
|
+
Source,
|
|
87
|
+
tsgoCheckers,
|
|
88
|
+
type CheckDecision,
|
|
89
|
+
type CheckFit,
|
|
90
|
+
type RunConfig,
|
|
91
|
+
} from './run-config.js';
|
|
92
|
+
import type { RecordShape } from './run-records.js';
|
|
93
|
+
|
|
94
|
+
// The tests of the scheduler read these here, next to the plans they choose between.
|
|
95
|
+
export { autoCheckers, libraryBoundCount };
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* `TSC_TS_SHARE` for a run without a run configuration (the emit threads, which share no program to
|
|
99
|
+
* translate against): `1` shares, `0` does not, and unset shares nothing (`sharePin`).
|
|
100
|
+
*/
|
|
101
|
+
const shareThreads = sharePin(process.env);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* TSC_TS_SHARE_STOP_AT_MS: `t` or `t:level` stops a sharing run's sharing `t` milliseconds in, whatever
|
|
105
|
+
* the run's numbers say, to check the switch: `2` (the default) stops posting and translating, `1`
|
|
106
|
+
* stops posting. Testing only.
|
|
107
|
+
*/
|
|
108
|
+
function shareStopHook(): { readonly at: number; readonly level: number } | undefined {
|
|
109
|
+
const value = process.env.TSC_TS_SHARE_STOP_AT_MS;
|
|
110
|
+
if (value === undefined || value === '') return undefined;
|
|
111
|
+
const colon = value.indexOf(':');
|
|
112
|
+
const at = Number(colon < 0 ? value : value.substring(0, colon));
|
|
113
|
+
if (!Number.isFinite(at) || at < 0) return undefined;
|
|
114
|
+
return { at, level: colon >= 0 && value.substring(colon + 1) === '1' ? 1 : 2 };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const shareStop = shareStopHook();
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The window a run without a configuration uses before the main thread weighs what sharing saved against
|
|
121
|
+
* what it cost (`sharingNumbers`): 300 ms. A run with one uses the larger of 200 ms and a tenth of the
|
|
122
|
+
* check time its record measured (`RunConfig.shareWindowMs`, `shareWindowOf`).
|
|
123
|
+
*/
|
|
124
|
+
const shareWindowMs = 300;
|
|
66
125
|
|
|
67
126
|
/** With TSC_TS_CHECKERS_TRACE: the process's resident and peak resident set in MB (Linux), else 0. */
|
|
68
127
|
export function residentMB(): number[] {
|
|
@@ -140,6 +199,11 @@ export class CheckerResult {
|
|
|
140
199
|
readonly units: number[] = [],
|
|
141
200
|
/** Those of `units` after which its checker had diagnostics in their file (`checkSourceFileRange`). */
|
|
142
201
|
readonly reportingUnits: number[] = [],
|
|
202
|
+
/**
|
|
203
|
+
* The types, instantiations and transient symbols its checker created, which `--diagnostics` adds to
|
|
204
|
+
* the main thread's (`CheckedProgram.addCheckerCounts`); empty for a result without a checker of its own.
|
|
205
|
+
*/
|
|
206
|
+
readonly counts: number[] = [],
|
|
143
207
|
) {}
|
|
144
208
|
}
|
|
145
209
|
|
|
@@ -184,6 +248,7 @@ export type PackedResult = {
|
|
|
184
248
|
readonly reserved: number[];
|
|
185
249
|
readonly units: Int32Array;
|
|
186
250
|
readonly reportingUnits: Int32Array;
|
|
251
|
+
readonly counts: number[];
|
|
187
252
|
};
|
|
188
253
|
|
|
189
254
|
class ResultPacker {
|
|
@@ -287,6 +352,7 @@ export function packResult(result: CheckerResult, program: Program): PackedResul
|
|
|
287
352
|
reserved: result.reserved,
|
|
288
353
|
units: Int32Array.from(result.units),
|
|
289
354
|
reportingUnits: Int32Array.from(result.reportingUnits),
|
|
355
|
+
counts: result.counts,
|
|
290
356
|
};
|
|
291
357
|
}
|
|
292
358
|
|
|
@@ -416,6 +482,7 @@ export function unpackResult(
|
|
|
416
482
|
emits,
|
|
417
483
|
Array.from(packed.units),
|
|
418
484
|
Array.from(packed.reportingUnits),
|
|
485
|
+
packed.counts,
|
|
419
486
|
);
|
|
420
487
|
}
|
|
421
488
|
|
|
@@ -454,23 +521,9 @@ function checkersEmit(checked: CheckedProgram, emits: boolean): boolean {
|
|
|
454
521
|
return emits && parallelCheck.writer !== undefined && options.noEmit !== true && options.listFilesOnly !== true;
|
|
455
522
|
}
|
|
456
523
|
|
|
457
|
-
/**
|
|
458
|
-
* How the number of checkers is chosen without `--checkers`: `auto` (the default) uses every CPU
|
|
459
|
-
* this process may run on (`os.availableParallelism()`, at most `maxAutoCheckers`), and at least
|
|
460
|
-
* tsgo's four; fewer than the CPUs, but never fewer than four, when the estimated peak memory of more
|
|
461
|
-
* checkers would not fit (`memoryCheckers`). `fixed` (TSC_TS_CHECKERS=fixed) uses tsgo's four.
|
|
462
|
-
*/
|
|
463
|
-
export const checkerPolicy: 'fixed' | 'auto' = process.env.TSC_TS_CHECKERS === 'fixed' ? 'fixed' : 'auto';
|
|
464
|
-
|
|
465
|
-
/** tsgo's number of checkers, and the fewest `auto` starts. */
|
|
466
|
-
const tsgoCheckers = 4;
|
|
467
|
-
|
|
468
|
-
/** The most checkers `auto` starts (measured up to 16 CPUs). */
|
|
469
|
-
const maxAutoCheckers = 16;
|
|
470
|
-
|
|
471
524
|
/**
|
|
472
525
|
* The number of checker threads for `checkers` (`--checkers`), or 1 to check on this thread. Without
|
|
473
|
-
* the option, native builds follow
|
|
526
|
+
* the option, native builds follow the checker policy (with `program`, the memory estimate of `auto`
|
|
474
527
|
* applies); under Node.js, where every thread builds its own copy of the program, tsc-ts checks on
|
|
475
528
|
* one thread.
|
|
476
529
|
*/
|
|
@@ -487,8 +540,8 @@ export function checkerThreads(
|
|
|
487
540
|
function defaultCheckers(program: Program | undefined): number {
|
|
488
541
|
if (!sharesPublishedGraphs) return 1;
|
|
489
542
|
if (program !== undefined && checksLittle(program)) return 1;
|
|
490
|
-
if (
|
|
491
|
-
const cpus = Math.min(
|
|
543
|
+
if (checkersPolicy(process.env) === 'fixed') return tsgoCheckers;
|
|
544
|
+
const cpus = Math.min(availableCpus(), maxAutoCheckers);
|
|
492
545
|
return autoCheckers(cpus, program === undefined ? Number.MAX_SAFE_INTEGER : memoryCheckers(program));
|
|
493
546
|
}
|
|
494
547
|
|
|
@@ -514,24 +567,6 @@ function checksLittle(program: Program): boolean {
|
|
|
514
567
|
return true;
|
|
515
568
|
}
|
|
516
569
|
|
|
517
|
-
/** `auto` with `cpus` CPUs when the memory estimate fits `fit` checkers: tsgo's four at least. */
|
|
518
|
-
export function autoCheckers(cpus: number, fit: number): number {
|
|
519
|
-
return Math.max(tsgoCheckers, Math.min(cpus, fit));
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* The number of threads that parse ahead of a program checked by `checkers` checkers: every CPU this
|
|
524
|
-
* process may run on, as tsgo parses on every core whatever its checker count (parse threads hand
|
|
525
|
-
* their files to the shared program; what else they allocate is reused by the checker threads), but
|
|
526
|
-
* one. The main thread hands out the chunks they load: with a thread on every CPU, it waits for a CPU
|
|
527
|
-
* each time a thread returns a chunk, and so does that thread for its next chunk (VS Code's parse took
|
|
528
|
-
* 18-57 ms longer with 16 threads on 16 CPUs than with 15).
|
|
529
|
-
*/
|
|
530
|
-
export function parseThreads(checkers: number): number {
|
|
531
|
-
const cpus = availableParallelism();
|
|
532
|
-
return Math.max(1, Math.min(cpus - 1, Math.max(checkers, Math.min(cpus, maxAutoCheckers))));
|
|
533
|
-
}
|
|
534
|
-
|
|
535
570
|
/**
|
|
536
571
|
* The program's memory per character of source text, where it can't be measured (`residentBytes`):
|
|
537
572
|
* 23-28 bytes on the benchmark apps when checking starts (parse threads' arenas included).
|
|
@@ -567,9 +602,14 @@ const lastResortShare = 0.95;
|
|
|
567
602
|
* bytes, for a program of `programBytes` (`firstCheckerShare`, `checkerBytesPerDeclarationChar`);
|
|
568
603
|
* at least 1.
|
|
569
604
|
*/
|
|
570
|
-
export function checkersFittingMemory(
|
|
571
|
-
|
|
572
|
-
|
|
605
|
+
export function checkersFittingMemory(
|
|
606
|
+
programBytes: number,
|
|
607
|
+
declarationChars: number,
|
|
608
|
+
memory: number,
|
|
609
|
+
peakShare: number = 1,
|
|
610
|
+
): number {
|
|
611
|
+
const peak = programBytes * (1 + firstCheckerShare) * peakShare;
|
|
612
|
+
const perChecker = Math.max(1, declarationChars * checkerBytesPerDeclarationChar * peakShare);
|
|
573
613
|
return Math.max(1, 1 + Math.floor((memoryShare * memory - peak) / perChecker));
|
|
574
614
|
}
|
|
575
615
|
|
|
@@ -586,59 +626,6 @@ export function memoryLimit(): number {
|
|
|
586
626
|
return limit;
|
|
587
627
|
}
|
|
588
628
|
|
|
589
|
-
/**
|
|
590
|
-
* The smallest memory limit of this process's cgroup and its ancestors (Linux), or 0 where there is
|
|
591
|
-
* none or it is unknown: `memory.max` and `memory.high` under cgroup v2, `memory.limit_in_bytes`
|
|
592
|
-
* under v1. `process.constrainedMemory()` does not always see these: libuv reads only the process's
|
|
593
|
-
* own cgroup, and scriptc's runtime only the root of the cgroup mount, which is the process's own
|
|
594
|
-
* cgroup only inside a cgroup namespace.
|
|
595
|
-
*/
|
|
596
|
-
export function cgroupMemoryLimit(): number {
|
|
597
|
-
let membership: string;
|
|
598
|
-
try {
|
|
599
|
-
membership = readFileSync('/proc/self/cgroup', 'utf8');
|
|
600
|
-
} catch {
|
|
601
|
-
return 0;
|
|
602
|
-
}
|
|
603
|
-
let limit = 0;
|
|
604
|
-
const lower = (value: number): void => {
|
|
605
|
-
if (value > 0 && (limit === 0 || value < limit)) limit = value;
|
|
606
|
-
};
|
|
607
|
-
for (const line of membership.split('\n')) {
|
|
608
|
-
// "0::/path" (v2), "N:memory:/path" or "N:cpu,memory:/path" (v1).
|
|
609
|
-
const first = line.indexOf(':');
|
|
610
|
-
const second = line.indexOf(':', first + 1);
|
|
611
|
-
if (first < 0 || second < 0) continue;
|
|
612
|
-
const controllers = line.substring(first + 1, second);
|
|
613
|
-
const path = line.substring(second + 1);
|
|
614
|
-
if (controllers === '') {
|
|
615
|
-
for (let dir = path; ; dir = dir.substring(0, dir.lastIndexOf('/'))) {
|
|
616
|
-
const base = '/sys/fs/cgroup' + (dir === '/' ? '' : dir);
|
|
617
|
-
lower(cgroupLimitValue(base + '/memory.max'));
|
|
618
|
-
lower(cgroupLimitValue(base + '/memory.high'));
|
|
619
|
-
if (dir === '/' || dir === '' || dir.lastIndexOf('/') < 0) break;
|
|
620
|
-
}
|
|
621
|
-
} else if (controllers.split(',').indexOf('memory') >= 0) {
|
|
622
|
-
lower(cgroupLimitValue('/sys/fs/cgroup/memory' + (path === '/' ? '' : path) + '/memory.limit_in_bytes'));
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
return limit;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/** A cgroup memory limit file's value in bytes, or 0 for `max`, an unreadable file or no number. */
|
|
629
|
-
function cgroupLimitValue(file: string): number {
|
|
630
|
-
let text: string;
|
|
631
|
-
try {
|
|
632
|
-
text = readFileSync(file, 'utf8').trim();
|
|
633
|
-
} catch {
|
|
634
|
-
return 0;
|
|
635
|
-
}
|
|
636
|
-
if (text === '' || text === 'max') return 0;
|
|
637
|
-
const value = Number(text);
|
|
638
|
-
// cgroup v1 reports no limit as a page-aligned value near 2^63.
|
|
639
|
-
return Number.isFinite(value) && value > 0 && value < 2 ** 62 ? value : 0;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
629
|
/** The program whose text lengths `textLengths` holds. */
|
|
643
630
|
let lengthsProgram: Program | undefined = undefined;
|
|
644
631
|
let lengthsOfProgram: number[] = [];
|
|
@@ -664,6 +651,27 @@ export function textLengths(program: Program): number[] {
|
|
|
664
651
|
* or an estimate from its text where that is unknown.
|
|
665
652
|
*/
|
|
666
653
|
export function memoryCheckers(program: Program): number {
|
|
654
|
+
const estimate = memoryEstimate(program);
|
|
655
|
+
const fit = checkersFittingMemory(estimate.programBytes, estimate.declarationChars, estimate.memory);
|
|
656
|
+
if (process.env.TSC_TS_CHECKERS_TRACE !== undefined)
|
|
657
|
+
process.stderr.write(
|
|
658
|
+
`checkers: ${estimate.chars} source chars (${estimate.declarationChars} in declaration files), program ${Math.round(estimate.programBytes / 1048576)}MB${estimate.resident > 0 ? '' : ' (estimated)'}, memory ${Math.round(estimate.memory / 1048576)}MB fits ${fit} checkers\n`,
|
|
659
|
+
);
|
|
660
|
+
return fit;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** What the memory fit reads of a program: its text, the declarations in it, and this process's memory. */
|
|
664
|
+
interface MemoryEstimate {
|
|
665
|
+
readonly chars: number;
|
|
666
|
+
readonly declarationChars: number;
|
|
667
|
+
readonly programBytes: number;
|
|
668
|
+
/** Whether `programBytes` is the resident set (`0` where it is an estimate from the text). */
|
|
669
|
+
readonly resident: number;
|
|
670
|
+
readonly memory: number;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** The memory estimate of `program`, of its text (`textLengths`, computed once) and this process's memory. */
|
|
674
|
+
function memoryEstimate(program: Program): MemoryEstimate {
|
|
667
675
|
let chars = 0;
|
|
668
676
|
let declarationChars = 0;
|
|
669
677
|
const files = program.getSourceFiles();
|
|
@@ -673,14 +681,24 @@ export function memoryCheckers(program: Program): number {
|
|
|
673
681
|
if (files[i]!.isDeclarationFile) declarationChars += length[i]!;
|
|
674
682
|
}
|
|
675
683
|
const resident = residentBytes();
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
+
return {
|
|
685
|
+
chars,
|
|
686
|
+
declarationChars,
|
|
687
|
+
programBytes: resident > 0 ? resident : chars * programBytesPerChar,
|
|
688
|
+
resident,
|
|
689
|
+
memory: memoryLimit(),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* The checkers whose estimated peak memory fits `program` (`checkersFittingMemory`), without sharing and
|
|
695
|
+
* with the peak sharing adds (`sharingPeakShare`), which the sharing rule requires of the count it keeps.
|
|
696
|
+
*/
|
|
697
|
+
export function memoryFit(program: Program): CheckFit {
|
|
698
|
+
const estimate = memoryEstimate(program);
|
|
699
|
+
return {
|
|
700
|
+
sharing: checkersFittingMemory(estimate.programBytes, estimate.declarationChars, estimate.memory, sharingPeakShare),
|
|
701
|
+
};
|
|
684
702
|
}
|
|
685
703
|
|
|
686
704
|
/**
|
|
@@ -1464,10 +1482,13 @@ export function planSchedule(
|
|
|
1464
1482
|
}
|
|
1465
1483
|
let all = 0;
|
|
1466
1484
|
for (const i of order) all += cost[i]!;
|
|
1467
|
-
// Files that cost more than a
|
|
1468
|
-
//
|
|
1485
|
+
// Files that cost more than a 128th of the program go first, costliest first: at the end of a range
|
|
1486
|
+
// they would make its checker finish last. A fixed fraction of the program, rather than of a
|
|
1487
|
+
// checker's share, keeps the cut in proportion to how many checkers drain the shared queue: it is a
|
|
1488
|
+
// 32nd of a share at 4 checkers and an eighth at 16, where a range is shorter and a file of the
|
|
1489
|
+
// same size would otherwise sit in it.
|
|
1469
1490
|
let heavyCount = count;
|
|
1470
|
-
while (heavyCount < order.length && cost[order[heavyCount]!]! *
|
|
1491
|
+
while (heavyCount < order.length && cost[order[heavyCount]!]! * 128 >= all) heavyCount++;
|
|
1471
1492
|
// The groups go to the shared queue as one entry each, among the costliest files, by total cost.
|
|
1472
1493
|
const groups = checkGroups(checked, count, groupLoad, costs);
|
|
1473
1494
|
const grouped = new Set<number>();
|
|
@@ -1650,9 +1671,13 @@ function threadSharingOf(
|
|
|
1650
1671
|
order: readonly number[],
|
|
1651
1672
|
units: readonly number[],
|
|
1652
1673
|
files: number,
|
|
1674
|
+
bounded: boolean,
|
|
1653
1675
|
): ThreadSharingData[] {
|
|
1654
1676
|
const takers = new SharedArrayBuffer(4 * files);
|
|
1655
1677
|
const forbidden = new SharedArrayBuffer(4 * files * forbiddenWords(count));
|
|
1678
|
+
// The checkers that missed on a key in each file, which a post requires (`ThreadSharing.demanded`). An unbounded
|
|
1679
|
+
// run posts as Phase 1 does and never reads it, so it gets a word rather than a bit per checker per file.
|
|
1680
|
+
const wanted = bounded ? new SharedArrayBuffer(4 * files * forbiddenWords(count)) : new SharedArrayBuffer(4);
|
|
1656
1681
|
const handoff = new SharedArrayBuffer(4 * (3 + order.length));
|
|
1657
1682
|
new Int32Array(handoff)[2] = count;
|
|
1658
1683
|
const taken = new Int32Array(takers);
|
|
@@ -1670,17 +1695,23 @@ function threadSharingOf(
|
|
|
1670
1695
|
const stringLogs: SharedArrayBuffer[] = [];
|
|
1671
1696
|
for (let i = 0; i < count; i++) {
|
|
1672
1697
|
numberLogs.push(new SharedArrayBuffer(4 * numberLogSize));
|
|
1673
|
-
recordTables.push(new SharedArrayBuffer(
|
|
1698
|
+
recordTables.push(new SharedArrayBuffer(4 * recordTableSize));
|
|
1674
1699
|
stringLogs.push(new SharedArrayBuffer(2 * stringLogSize));
|
|
1675
1700
|
}
|
|
1676
|
-
const used = new SharedArrayBuffer(
|
|
1701
|
+
const used = new SharedArrayBuffer(4 * usedStride * count);
|
|
1702
|
+
// The stop switch (0 runs, 1 stops posting, 2 stops posting and translating) and the counters the
|
|
1703
|
+
// main thread reads at its window, a row per checker.
|
|
1704
|
+
const control = new SharedArrayBuffer(4);
|
|
1705
|
+
const stats = new SharedArrayBuffer(8 * shareStatColumns * count);
|
|
1677
1706
|
const data: ThreadSharingData[] = [];
|
|
1678
1707
|
for (let i = 0; i < count; i++)
|
|
1679
1708
|
data.push({
|
|
1680
1709
|
index: i,
|
|
1681
1710
|
count,
|
|
1711
|
+
bounded,
|
|
1682
1712
|
takers,
|
|
1683
1713
|
forbidden,
|
|
1714
|
+
wanted,
|
|
1684
1715
|
handoff,
|
|
1685
1716
|
typeTable,
|
|
1686
1717
|
returnTable,
|
|
@@ -1690,6 +1721,8 @@ function threadSharingOf(
|
|
|
1690
1721
|
recordTables,
|
|
1691
1722
|
stringLogs,
|
|
1692
1723
|
used,
|
|
1724
|
+
control,
|
|
1725
|
+
stats,
|
|
1693
1726
|
});
|
|
1694
1727
|
return data;
|
|
1695
1728
|
}
|
|
@@ -1903,6 +1936,7 @@ export function checkFromQueue(
|
|
|
1903
1936
|
emits,
|
|
1904
1937
|
unitsDone,
|
|
1905
1938
|
reportingUnits,
|
|
1939
|
+
onMainThread ? [] : [checked.checker.typeCount, checked.checker.totalInstantiations, checked.checker.symbolCount],
|
|
1906
1940
|
);
|
|
1907
1941
|
}
|
|
1908
1942
|
|
|
@@ -1920,50 +1954,179 @@ export function mainThreadChecks(shared: boolean): boolean {
|
|
|
1920
1954
|
}
|
|
1921
1955
|
|
|
1922
1956
|
/**
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1957
|
+
* Whether the run reads the memory fit of sharing (`checkDecisionOf`): the static rule reads it where
|
|
1958
|
+
* the signals point at sharing, and a run the learning rules read may adopt sharing the static rule
|
|
1959
|
+
* leaves off, or explore it, when its samples show the configuration faster (DESIGN 4.3-4.4).
|
|
1960
|
+
*
|
|
1961
|
+
* A pin on sharing chooses sharing without a rule, but where it pins sharing on the learning rules still
|
|
1962
|
+
* compare checker counts with it, and every count they may adopt reads how many fit memory with sharing's
|
|
1963
|
+
* peak: a pinned count that no rule can change, and `TSC_TS_AUTO=0`, read nothing.
|
|
1926
1964
|
*/
|
|
1927
|
-
|
|
1928
|
-
|
|
1965
|
+
export function sharingFitNeeded(config: RunConfig, signals: CheckSignals): boolean {
|
|
1966
|
+
if (config.automaticity === Automaticity.Off) return false;
|
|
1967
|
+
if (config.shareSource === Source.Pinned) {
|
|
1968
|
+
return (
|
|
1969
|
+
config.share &&
|
|
1970
|
+
config.checkersSource !== Source.Pinned &&
|
|
1971
|
+
learns(config.automaticity) &&
|
|
1972
|
+
config.record !== undefined
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
if (sharingCandidate(signals)) return true;
|
|
1976
|
+
return learns(config.automaticity) && config.record !== undefined;
|
|
1977
|
+
}
|
|
1929
1978
|
|
|
1930
1979
|
/**
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
* `
|
|
1980
|
+
* What `decideCheck` needs of the run besides its signals: whether the checker threads of a shared
|
|
1981
|
+
* program may share results (`startParallelCheck` makes the same test), and, where sharing is possible
|
|
1982
|
+
* and a rule that may choose it reads the estimate, how many checkers fit memory with sharing's peak
|
|
1983
|
+
* (`sharingFitNeeded`). The estimate costs a pass over the program's files and its memory limit, so it
|
|
1984
|
+
* is computed only where a rule reads it: a run without a record pays only where the static rule's
|
|
1985
|
+
* signals point at sharing, and one checker, a program that is not shared, a pinned run or
|
|
1986
|
+
* `TSC_TS_AUTO=0` pay nothing.
|
|
1987
|
+
*/
|
|
1988
|
+
function checkDecisionOf(config: RunConfig, signals: CheckSignals, program: Program, shared: boolean): CheckDecision {
|
|
1989
|
+
// `mainThreadChecks` is false whenever the program is shared (`!shared`), as sharing needs.
|
|
1990
|
+
const possible = shared && signals.count > 1 && signals.count <= maxSharingCheckers;
|
|
1991
|
+
const fit = possible && sharingFitNeeded(config, signals) ? memoryFit(program) : undefined;
|
|
1992
|
+
// A run whose record the learning rules read also names the shape its samples describe, the CPUs a
|
|
1993
|
+
// count may use, the memory a configuration may not exceed and the time a control sample ages from.
|
|
1994
|
+
const learning = config.records || config.record !== undefined;
|
|
1995
|
+
return {
|
|
1996
|
+
possible,
|
|
1997
|
+
fit,
|
|
1998
|
+
shape: learning ? recordShapeOf(program, signals) : undefined,
|
|
1999
|
+
cpus: Math.min(availableCpus(), maxAutoCheckers),
|
|
2000
|
+
memoryBytes: memoryLimit(),
|
|
2001
|
+
now: Math.floor(Date.now() / 1000),
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
/** The program's shape for its record (`RecordShape`), with the shares the sharing rule reads as `F`. */
|
|
2006
|
+
function recordShapeOf(program: Program, signals: CheckSignals): RecordShape {
|
|
2007
|
+
const files = program.getSourceFiles();
|
|
2008
|
+
let chars = 0;
|
|
2009
|
+
let declarationChars = 0;
|
|
2010
|
+
for (const file of files) {
|
|
2011
|
+
chars += file.end;
|
|
2012
|
+
if (file.isDeclarationFile) declarationChars += file.end;
|
|
2013
|
+
}
|
|
2014
|
+
return {
|
|
2015
|
+
files: files.length,
|
|
2016
|
+
chars,
|
|
2017
|
+
declarationChars,
|
|
2018
|
+
checkChars: chars - declarationChars,
|
|
2019
|
+
library: Math.round(signals.library),
|
|
2020
|
+
own: Math.round(signals.own),
|
|
2021
|
+
redundancy: signals.redundancy,
|
|
2022
|
+
fileShare: Math.max(signals.declarationShare, signals.nodeModulesShare),
|
|
2023
|
+
};
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
/** What the counters of `count` checkers say at the sharing window: milliseconds saved and spent. */
|
|
2027
|
+
interface SharingNumbers {
|
|
2028
|
+
/** What taking posted results saved, over the checkers: the computations they did not repeat. */
|
|
2029
|
+
readonly saved: number;
|
|
2030
|
+
/**
|
|
2031
|
+
* What posting cost: exporting and translating results. Translating a result reads its records in
|
|
2032
|
+
* place inside `ThreadSharing.translatePosted`'s timed translation, so `translationMs` holds that
|
|
2033
|
+
* reading too; counting a separate unpacking time would charge the window for it twice.
|
|
2034
|
+
*/
|
|
2035
|
+
readonly spent: number;
|
|
2036
|
+
readonly posted: number;
|
|
2037
|
+
readonly translated: number;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/**
|
|
2041
|
+
* The counters of `count` checkers' rows of `stats` (`ThreadSharing.writeStats`), summed. The tests read
|
|
2042
|
+
* this, next to the window it feeds.
|
|
2043
|
+
*/
|
|
2044
|
+
export function sharingNumbers(stats: Float64Array, count: number): SharingNumbers {
|
|
2045
|
+
let saved = 0;
|
|
2046
|
+
let spent = 0;
|
|
2047
|
+
let posted = 0;
|
|
2048
|
+
let translated = 0;
|
|
2049
|
+
for (let i = 0; i < count; i++) {
|
|
2050
|
+
const row = i * shareStatColumns;
|
|
2051
|
+
saved += stats[row + shareStatSaved]!;
|
|
2052
|
+
spent += stats[row + shareStatExport]! + stats[row + shareStatTranslation]!;
|
|
2053
|
+
posted += stats[row + shareStatPosted]!;
|
|
2054
|
+
translated += stats[row + shareStatTranslated]!;
|
|
2055
|
+
}
|
|
2056
|
+
return { saved, spent, posted, translated };
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
/**
|
|
2060
|
+
* The control a sharing run's numbers ask for: 2 when it saved less than 1.5 times what it spent, 1
|
|
2061
|
+
* when the posts go untaken (under 5% of them, and at least one), else 0. Stopping takes a minute at
|
|
2062
|
+
* worst, when the window's numbers are still too early to tell.
|
|
2063
|
+
*/
|
|
2064
|
+
export function stopLevel(numbers: SharingNumbers): number {
|
|
2065
|
+
if (numbers.saved < 1.5 * numbers.spent) return 2;
|
|
2066
|
+
if (numbers.posted > 0 && numbers.translated / numbers.posted < 0.05) return 1;
|
|
2067
|
+
return 0;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/**
|
|
2071
|
+
* Whether the rules of a run of `config` at `count` checkers read the check signals (`checkSignals`).
|
|
2072
|
+
* Two rules read them:
|
|
1935
2073
|
*
|
|
1936
|
-
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
*
|
|
2074
|
+
* - the library-bound cut (`decideCheck`), wherever the count is not pinned and is above tsgo's four.
|
|
2075
|
+
* This is the decision a run made before the automatic selection, and `TSC_TS_AUTO=0` keeps it: with
|
|
2076
|
+
* selection off the count is not cut for sharing either, but dropping the signals would leave the
|
|
2077
|
+
* cut nothing to read and start checkers the cut would not;
|
|
2078
|
+
* - the sharing rule, at `minSharingCheckers` or more, where neither the count nor sharing is pinned
|
|
2079
|
+
* (the cut may leave the count sharing was measured at, so the signals are read before it).
|
|
2080
|
+
*
|
|
2081
|
+
* A pinned count and a pinned sharing read neither, and pay nothing for the pass over the ranges.
|
|
1944
2082
|
*/
|
|
1945
|
-
export function
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2083
|
+
export function signalsNeeded(config: RunConfig, count: number): boolean {
|
|
2084
|
+
const cut = config.checkersSource === Source.Default && count > tsgoCheckers;
|
|
2085
|
+
if (config.automaticity === Automaticity.Off) return cut;
|
|
2086
|
+
return cut || (config.shareSource === Source.Default && count >= minSharingCheckers);
|
|
1949
2087
|
}
|
|
1950
2088
|
|
|
1951
2089
|
/**
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
2090
|
+
* What `plan`, made with `count` checkers, shows about the cost of checking (`CheckSignals`): the
|
|
2091
|
+
* libraries a range reaches and the files it checks, the share of that a second range reaches too, and
|
|
2092
|
+
* the shares of declaration and node_modules files of the program. Computed where a rule may read them
|
|
2093
|
+
* (`signalsNeeded`) and left at zero elsewhere: a pinned count needs none of them, and the pass over
|
|
2094
|
+
* every range's libraries costs a millisecond on a large program.
|
|
1954
2095
|
*/
|
|
1955
|
-
function
|
|
1956
|
-
|
|
2096
|
+
function checkSignals(
|
|
2097
|
+
checked: CheckedProgram,
|
|
2098
|
+
plan: CheckPlan,
|
|
2099
|
+
costs: CheckCosts,
|
|
2100
|
+
count: number,
|
|
2101
|
+
config: RunConfig | undefined = undefined,
|
|
2102
|
+
): CheckSignals {
|
|
2103
|
+
const ranges = plan.ranges.length >> 1;
|
|
2104
|
+
const signals = new CheckSignals(count, ranges);
|
|
1957
2105
|
const program = checked.program;
|
|
2106
|
+
// What a rule would read, what the run's record writes (`config.records`), or what a doctor child
|
|
2107
|
+
// reports (`config.measure`), is computed; a pinned count with records off and no report pays nothing.
|
|
2108
|
+
const deciding =
|
|
2109
|
+
config === undefined
|
|
2110
|
+
? checkersPolicy(process.env) === 'auto' && program.options.checkers === undefined && count > tsgoCheckers
|
|
2111
|
+
: signalsNeeded(config, count);
|
|
2112
|
+
const wanted = deciding || (config !== undefined && (config.records || config.measure));
|
|
2113
|
+
if (!wanted || ranges === 0) return signals;
|
|
1958
2114
|
const files = program.getSourceFiles();
|
|
1959
2115
|
const length = textLengths(program);
|
|
1960
2116
|
const isChecked: boolean[] = [];
|
|
1961
|
-
|
|
2117
|
+
let declarationFiles = 0;
|
|
2118
|
+
let nodeModulesFiles = 0;
|
|
2119
|
+
for (let i = 0; i < files.length; i++) {
|
|
2120
|
+
isChecked.push(false);
|
|
2121
|
+
if (files[i]!.isDeclarationFile) declarationFiles++;
|
|
2122
|
+
if (files[i]!.path.indexOf('/node_modules/') >= 0) nodeModulesFiles++;
|
|
2123
|
+
}
|
|
1962
2124
|
for (const i of costs.order) isChecked[i] = true;
|
|
1963
|
-
const ranges = plan.ranges.length >> 1;
|
|
1964
|
-
if (ranges === 0) return count;
|
|
1965
2125
|
let library = 0;
|
|
1966
2126
|
let own = 0;
|
|
2127
|
+
// The libraries every range reaches together, each counted once (`CheckSignals.redundancy`).
|
|
2128
|
+
const union = new Set<number>();
|
|
2129
|
+
let unionChars = 0;
|
|
1967
2130
|
for (let r = 0; r < ranges; r++) {
|
|
1968
2131
|
// The libraries the range's files import, and what they reach through other libraries (as checkCosts counts them).
|
|
1969
2132
|
const reached = new Set<number>();
|
|
@@ -1980,6 +2143,10 @@ function libraryBoundCheckers(checked: CheckedProgram, plan: CheckPlan, costs: C
|
|
|
1980
2143
|
while (stack.length > 0) {
|
|
1981
2144
|
const k = stack.pop()!;
|
|
1982
2145
|
library += length[k]!;
|
|
2146
|
+
if (!union.has(k)) {
|
|
2147
|
+
union.add(k);
|
|
2148
|
+
unionChars += length[k]!;
|
|
2149
|
+
}
|
|
1983
2150
|
for (const t of costs.imports[k] ?? [])
|
|
1984
2151
|
if (
|
|
1985
2152
|
!reached.has(t) &&
|
|
@@ -1992,18 +2159,22 @@ function libraryBoundCheckers(checked: CheckedProgram, plan: CheckPlan, costs: C
|
|
|
1992
2159
|
}
|
|
1993
2160
|
}
|
|
1994
2161
|
}
|
|
1995
|
-
|
|
2162
|
+
signals.library = library / ranges;
|
|
2163
|
+
signals.own = own / ranges;
|
|
2164
|
+
signals.redundancy = (library - unionChars) / (library + own);
|
|
2165
|
+
signals.declarationShare = declarationFiles / files.length;
|
|
2166
|
+
signals.nodeModulesShare = nodeModulesFiles / files.length;
|
|
2167
|
+
if (config !== undefined) config.signals = signals;
|
|
1996
2168
|
if (process.env.TSC_TS_CHECKERS_TRACE !== undefined)
|
|
1997
2169
|
process.stderr.write(
|
|
1998
|
-
`checkers: ranges reach ${Math.round(library /
|
|
2170
|
+
`checkers: ranges reach ${Math.round(signals.library / 1000)}K library chars and check ${Math.round(signals.own / 1000)}K on average, ${Math.round(signals.redundancy * 100)}% of it reached twice: ${libraryBoundCount(count, signals.library, signals.own)} checkers\n`,
|
|
1999
2171
|
);
|
|
2000
|
-
return
|
|
2172
|
+
return signals;
|
|
2001
2173
|
}
|
|
2002
2174
|
|
|
2003
2175
|
/**
|
|
2004
|
-
* Checks `checked` with `checkers` checkers (fewer where
|
|
2005
|
-
*
|
|
2006
|
-
* `checked` holds every file's diagnostics.
|
|
2176
|
+
* Checks `checked` with `checkers` checkers (fewer where the configuration chooses so: `decideCheck`,
|
|
2177
|
+
* run-config.ts) and calls `done` with the wall time once `checked` holds every file's diagnostics.
|
|
2007
2178
|
*/
|
|
2008
2179
|
/**
|
|
2009
2180
|
* Shares the program of `checked` with checker threads (scriptc builds): this thread's checker merges the
|
|
@@ -2037,7 +2208,8 @@ export function shareWithCheckers(checked: CheckedProgram): void {
|
|
|
2037
2208
|
* `emitsTo`, the emit results go only there, by file index, and nothing else the threads find reaches
|
|
2038
2209
|
* the program: a declaration thread (parallel-emit.ts), while this thread's checker checks. With
|
|
2039
2210
|
* `declarationsTo`, the same holds for the declaration errors of a `noEmit` program
|
|
2040
|
-
* (`checkersReportDeclarations`).
|
|
2211
|
+
* (`checkersReportDeclarations`). `config` is the run's configuration: the count it chose is decided
|
|
2212
|
+
* here, once the program is bound and the plan made, and sharing is what it holds.
|
|
2041
2213
|
*/
|
|
2042
2214
|
export function startParallelCheck(
|
|
2043
2215
|
checked: CheckedProgram,
|
|
@@ -2048,6 +2220,7 @@ export function startParallelCheck(
|
|
|
2048
2220
|
checking: boolean = true,
|
|
2049
2221
|
emitsTo: (EmitResult | undefined)[] | undefined = undefined,
|
|
2050
2222
|
declarationsTo: (Diagnostic[] | undefined)[] | undefined = undefined,
|
|
2223
|
+
config: RunConfig | undefined = undefined,
|
|
2051
2224
|
): void {
|
|
2052
2225
|
let count = checkers;
|
|
2053
2226
|
const start = performance.now();
|
|
@@ -2067,7 +2240,12 @@ export function startParallelCheck(
|
|
|
2067
2240
|
const orderStart = performance.now();
|
|
2068
2241
|
const costs = costOrder(checked);
|
|
2069
2242
|
let plan = planSchedule(checked, count, costs, checking);
|
|
2070
|
-
const
|
|
2243
|
+
const signals = checkSignals(checked, plan, costs, count, config);
|
|
2244
|
+
if (config !== undefined) config.signals = signals;
|
|
2245
|
+
const fewer =
|
|
2246
|
+
config === undefined
|
|
2247
|
+
? libraryBoundCount(signals.count, signals.library, signals.own)
|
|
2248
|
+
: decideCheck(config, signals, checkDecisionOf(config, signals, program, shared));
|
|
2071
2249
|
if (fewer < count) {
|
|
2072
2250
|
count = fewer;
|
|
2073
2251
|
plan = planSchedule(checked, count, costs, checking);
|
|
@@ -2109,7 +2287,13 @@ export function startParallelCheck(
|
|
|
2109
2287
|
const firstThread = mainChecks ? 1 : 0;
|
|
2110
2288
|
let remaining = count - firstThread;
|
|
2111
2289
|
let ownDone = false;
|
|
2290
|
+
/** The sharing window's timer, where the threads share results; cleared when they are done. */
|
|
2291
|
+
let shareStopTimer: ReturnType<typeof setTimeout> | undefined = undefined;
|
|
2112
2292
|
const finish = (): void => {
|
|
2293
|
+
if (shareStopTimer !== undefined) {
|
|
2294
|
+
clearTimeout(shareStopTimer);
|
|
2295
|
+
shareStopTimer = undefined;
|
|
2296
|
+
}
|
|
2113
2297
|
if (emitsTo !== undefined || declarationsTo !== undefined) {
|
|
2114
2298
|
mark('emitted on checker threads');
|
|
2115
2299
|
done(performance.now() - start);
|
|
@@ -2151,6 +2335,7 @@ export function startParallelCheck(
|
|
|
2151
2335
|
done(performance.now() - start);
|
|
2152
2336
|
};
|
|
2153
2337
|
const record = (result: CheckerResult): void => {
|
|
2338
|
+
checked.addCheckerCounts(result.counts);
|
|
2154
2339
|
if (declarationsTo !== undefined) {
|
|
2155
2340
|
for (let k = 0; k < result.declarations.length; k++) declarationsTo[result.files[k]!] = result.declarations[k]!;
|
|
2156
2341
|
if (trace)
|
|
@@ -2188,11 +2373,43 @@ export function startParallelCheck(
|
|
|
2188
2373
|
if (remaining === 0 && ownDone) finish();
|
|
2189
2374
|
};
|
|
2190
2375
|
const diagnosticFiles = new Map<string, DiagnosticFile>();
|
|
2191
|
-
//
|
|
2376
|
+
// Whether checker threads share results (shared-results.ts), which the run's configuration decides;
|
|
2377
|
+
// the emit threads of parallel-emit.ts have no configuration and never share (count 1).
|
|
2378
|
+
const sharesResults = config === undefined ? shareThreads === true : config.share;
|
|
2192
2379
|
const sharing =
|
|
2193
|
-
|
|
2194
|
-
? threadSharingOf(count, order, units, files.length)
|
|
2380
|
+
sharesResults && shared && !mainChecks && count > 1 && count <= maxSharingCheckers
|
|
2381
|
+
? threadSharingOf(count, order, units, files.length, config !== undefined && config.bounded)
|
|
2195
2382
|
: undefined;
|
|
2383
|
+
if (sharing !== undefined) {
|
|
2384
|
+
// The main thread is idle while the checkers run, so a timer reads their counters at the window and
|
|
2385
|
+
// stops what does not pay (control 1 or 2); every checker computes what it does not take, so the
|
|
2386
|
+
// diagnostics stay the same.
|
|
2387
|
+
const control = new Int32Array(sharing[0]!.control);
|
|
2388
|
+
const stats = new Float64Array(sharing[0]!.stats);
|
|
2389
|
+
const stop = shareStop;
|
|
2390
|
+
const at = stop !== undefined ? stop.at : config !== undefined ? config.shareWindowMs : shareWindowMs;
|
|
2391
|
+
shareStopTimer = setTimeout(() => {
|
|
2392
|
+
const numbers = sharingNumbers(stats, count);
|
|
2393
|
+
if (stop !== undefined) Atomics.store(control, 0, stop.level);
|
|
2394
|
+
else {
|
|
2395
|
+
const level = stopLevel(numbers);
|
|
2396
|
+
if (level === 0) return;
|
|
2397
|
+
Atomics.store(control, 0, level);
|
|
2398
|
+
}
|
|
2399
|
+
// The section says what the window saw, instead of the rule that turned sharing on.
|
|
2400
|
+
if (config !== undefined) {
|
|
2401
|
+
config.shareSource = Source.Measured;
|
|
2402
|
+
config.shareReasons.length = 0;
|
|
2403
|
+
config.shareReasons.push(
|
|
2404
|
+
`stopped after ${at} ms: ${Math.round(numbers.saved)} ms saved for ${Math.round(numbers.spent)} ms spent`,
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
if (trace)
|
|
2408
|
+
process.stderr.write(
|
|
2409
|
+
`checkers: stopped sharing after ${at} ms: ${Math.round(numbers.saved)} ms saved for ${Math.round(numbers.spent)} ms spent, ${numbers.translated}/${numbers.posted} posted results taken\n`,
|
|
2410
|
+
);
|
|
2411
|
+
}, at);
|
|
2412
|
+
}
|
|
2196
2413
|
const sharingStarted: boolean[] = [];
|
|
2197
2414
|
for (let i = 0; i < count; i++) sharingStarted.push(false);
|
|
2198
2415
|
const startSharingOf = (i: number): ThreadSharingData => {
|
|
@@ -2232,7 +2449,7 @@ export function startParallelCheck(
|
|
|
2232
2449
|
});
|
|
2233
2450
|
worker.on('error', (error: unknown) => {
|
|
2234
2451
|
// Sharing results (TSC_TS_SHARE) is still checked for such failures, which the main thread hides otherwise.
|
|
2235
|
-
if (trace ||
|
|
2452
|
+
if (trace || sharesResults) process.stderr.write(`checkers: thread ${i} failed: ${String(error)}\n`);
|
|
2236
2453
|
threadDone();
|
|
2237
2454
|
});
|
|
2238
2455
|
};
|
|
@@ -2362,6 +2579,7 @@ class Revival {
|
|
|
2362
2579
|
copy.emits.map((emit) => this.emit(emit)),
|
|
2363
2580
|
copy.units,
|
|
2364
2581
|
copy.reportingUnits,
|
|
2582
|
+
copy.counts,
|
|
2365
2583
|
);
|
|
2366
2584
|
}
|
|
2367
2585
|
|