Skip to content

Commit acc1667

Browse files
authored
Fixes to make tsc -b and incremental work for memory issues (#1745)
1 parent 0b9f3e7 commit acc1667

File tree

37 files changed

+527
-603
lines changed

37 files changed

+527
-603
lines changed

internal/execute/build/buildtask.go

Lines changed: 62 additions & 66 deletions
Large diffs are not rendered by default.

internal/execute/build/orchestrator.go

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/microsoft/typescript-go/internal/compiler"
1212
"github.com/microsoft/typescript-go/internal/core"
1313
"github.com/microsoft/typescript-go/internal/diagnostics"
14-
"github.com/microsoft/typescript-go/internal/execute/incremental"
1514
"github.com/microsoft/typescript-go/internal/execute/tsc"
1615
"github.com/microsoft/typescript-go/internal/tsoptions"
1716
"github.com/microsoft/typescript-go/internal/tspath"
@@ -28,7 +27,6 @@ type orchestratorResult struct {
2827
result tsc.CommandLineResult
2928
errors []*ast.Diagnostic
3029
statistics tsc.Statistics
31-
programStats []*tsc.Statistics
3230
filesToDelete []string
3331
}
3432

@@ -47,13 +45,10 @@ func (b *orchestratorResult) report(o *Orchestrator) {
4745
}), ""),
4846
))
4947
}
50-
if len(b.programStats) == 0 {
51-
return
52-
}
5348
if !o.opts.Command.CompilerOptions.Diagnostics.IsTrue() && !o.opts.Command.CompilerOptions.ExtendedDiagnostics.IsTrue() {
5449
return
5550
}
56-
b.statistics.Aggregate(b.programStats, o.opts.Sys.SinceStart())
51+
b.statistics.SetTotalTime(o.opts.Sys.SinceStart())
5752
b.statistics.Report(o.opts.Sys.Writer(), o.opts.Testing)
5853
}
5954

@@ -114,23 +109,20 @@ func (o *Orchestrator) createBuildTasks(oldTasks *collections.SyncMap[tspath.Pat
114109
wg.Queue(func() {
115110
path := o.toPath(config)
116111
var task *buildTask
117-
var program *incremental.Program
118112
var buildInfo *buildInfoEntry
119113
if oldTasks != nil {
120114
if existing, ok := oldTasks.Load(path); ok {
121115
if !existing.dirty {
122116
// Reuse existing task if config is same
123117
task = existing
124118
} else {
125-
program = existing.program
126119
buildInfo = existing.buildInfoEntry
127120
}
128121
}
129122
}
130123
if task == nil {
131124
task = &buildTask{config: config, isInitialCycle: oldTasks == nil}
132125
task.pending.Store(true)
133-
task.program = program
134126
task.buildInfoEntry = buildInfo
135127
}
136128
if _, loaded := o.tasks.LoadOrStore(path, task); loaded {
@@ -302,8 +294,7 @@ func (o *Orchestrator) DoCycle() {
302294
}
303295

304296
func (o *Orchestrator) buildOrClean() tsc.CommandLineResult {
305-
build := !o.opts.Command.BuildOptions.Clean.IsTrue()
306-
if build && o.opts.Command.BuildOptions.Verbose.IsTrue() {
297+
if !o.opts.Command.BuildOptions.Clean.IsTrue() && o.opts.Command.BuildOptions.Verbose.IsTrue() {
307298
o.createBuilderStatusReporter(nil)(ast.NewCompilerDiagnostic(
308299
diagnostics.Projects_in_this_build_Colon_0,
309300
strings.Join(core.Map(o.Order(), func(p string) string {
@@ -313,23 +304,14 @@ func (o *Orchestrator) buildOrClean() tsc.CommandLineResult {
313304
}
314305
var buildResult orchestratorResult
315306
if len(o.errors) == 0 {
316-
wg := core.NewWorkGroup(o.opts.Command.CompilerOptions.SingleThreaded.IsTrue())
317-
o.tasks.Range(func(path tspath.Path, task *buildTask) bool {
318-
task.reportStatus = o.createBuilderStatusReporter(task)
319-
task.diagnosticReporter = o.createDiagnosticReporter(task)
320-
wg.Queue(func() {
321-
if build {
322-
task.buildProject(o, path)
323-
} else {
324-
task.cleanProject(o, path)
325-
}
326-
task.report(o, path, &buildResult)
327-
})
328-
return true
329-
})
330-
wg.RunAndWait()
331307
buildResult.statistics.Projects = len(o.Order())
308+
if o.opts.Command.CompilerOptions.SingleThreaded.IsTrue() {
309+
o.singleThreadedBuildOrClean(&buildResult)
310+
} else {
311+
o.multiThreadedBuildOrClean(&buildResult)
312+
}
332313
} else {
314+
// Circularity errors prevent any project from being built
333315
buildResult.result.Status = tsc.ExitStatusProjectReferenceCycle_OutputsSkipped
334316
reportDiagnostic := o.createDiagnosticReporter(nil)
335317
for _, err := range o.errors {
@@ -341,11 +323,44 @@ func (o *Orchestrator) buildOrClean() tsc.CommandLineResult {
341323
return buildResult.result
342324
}
343325

326+
func (o *Orchestrator) singleThreadedBuildOrClean(buildResult *orchestratorResult) {
327+
// Go in the order since only one project can be built at a time so that random order isnt picked by work group creating deadlock
328+
for _, config := range o.Order() {
329+
path := o.toPath(config)
330+
task := o.getTask(path)
331+
o.buildOrCleanProject(task, path, buildResult)
332+
}
333+
}
334+
335+
func (o *Orchestrator) multiThreadedBuildOrClean(buildResult *orchestratorResult) {
336+
// Spin off the threads with waiting on upstream to build before actual project build
337+
wg := core.NewWorkGroup(false)
338+
o.tasks.Range(func(path tspath.Path, task *buildTask) bool {
339+
wg.Queue(func() {
340+
o.buildOrCleanProject(task, path, buildResult)
341+
})
342+
return true
343+
})
344+
wg.RunAndWait()
345+
}
346+
347+
func (o *Orchestrator) buildOrCleanProject(task *buildTask, path tspath.Path, buildResult *orchestratorResult) {
348+
task.result = &taskResult{}
349+
task.result.reportStatus = o.createBuilderStatusReporter(task)
350+
task.result.diagnosticReporter = o.createDiagnosticReporter(task)
351+
if !o.opts.Command.BuildOptions.Clean.IsTrue() {
352+
task.buildProject(o, path)
353+
} else {
354+
task.cleanProject(o, path)
355+
}
356+
task.report(o, path, buildResult)
357+
}
358+
344359
func (o *Orchestrator) getWriter(task *buildTask) io.Writer {
345360
if task == nil {
346361
return o.opts.Sys.Writer()
347362
}
348-
return &task.builder
363+
return &task.result.builder
349364
}
350365

351366
func (o *Orchestrator) createBuilderStatusReporter(task *buildTask) tsc.DiagnosticReporter {

internal/execute/incremental/emitfileshandler.go

Lines changed: 112 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package incremental
22

33
import (
44
"context"
5+
"sync/atomic"
56
"time"
67

78
"github.com/microsoft/typescript-go/internal/ast"
@@ -26,6 +27,7 @@ type emitFilesHandler struct {
2627
latestChangedDtsFiles collections.SyncMap[tspath.Path, string]
2728
deletedPendingKinds collections.Set[tspath.Path]
2829
emitUpdates collections.SyncMap[tspath.Path, *emitUpdate]
30+
hasEmitDiagnostics atomic.Bool
2931
}
3032

3133
// Determining what all is pending to be emitted based on previous options or previous file emit flags
@@ -44,14 +46,57 @@ func (h *emitFilesHandler) getPendingEmitKindForEmitOptions(emitKind FileEmitKin
4446
// The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
4547
// in that order would be used to write the files
4648
func (h *emitFilesHandler) emitAllAffectedFiles(options compiler.EmitOptions) *compiler.EmitResult {
49+
// Emit all affected files
50+
if h.program.snapshot.canUseIncrementalState() {
51+
results := h.emitFilesIncremental(options)
52+
if h.isForDtsErrors {
53+
if options.TargetSourceFile != nil {
54+
// Result from cache
55+
diagnostics, _ := h.program.snapshot.emitDiagnosticsPerFile.Load(options.TargetSourceFile.Path())
56+
return &compiler.EmitResult{
57+
EmitSkipped: true,
58+
Diagnostics: diagnostics.getDiagnostics(h.program.program, options.TargetSourceFile),
59+
}
60+
}
61+
return compiler.CombineEmitResults(results)
62+
} else {
63+
// Combine results and update buildInfo
64+
result := compiler.CombineEmitResults(results)
65+
h.emitBuildInfo(options, result)
66+
return result
67+
}
68+
} else if !h.isForDtsErrors {
69+
result := h.program.program.Emit(h.ctx, h.getEmitOptions(options))
70+
h.updateSnapshot()
71+
h.emitBuildInfo(options, result)
72+
return result
73+
} else {
74+
result := &compiler.EmitResult{
75+
EmitSkipped: true,
76+
Diagnostics: h.program.program.GetDeclarationDiagnostics(h.ctx, options.TargetSourceFile),
77+
}
78+
if len(result.Diagnostics) != 0 {
79+
h.program.snapshot.hasEmitDiagnostics = true
80+
}
81+
return result
82+
}
83+
}
84+
85+
func (h *emitFilesHandler) emitBuildInfo(options compiler.EmitOptions, result *compiler.EmitResult) {
86+
buildInfoResult := h.program.emitBuildInfo(h.ctx, options)
87+
if buildInfoResult != nil {
88+
result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...)
89+
result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...)
90+
}
91+
}
92+
93+
func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) []*compiler.EmitResult {
4794
// Get all affected files
4895
collectAllAffectedFiles(h.ctx, h.program)
4996
if h.ctx.Err() != nil {
5097
return nil
5198
}
5299

53-
// Emit all affected files
54-
var results []*compiler.EmitResult
55100
wg := core.NewWorkGroup(h.program.program.SingleThreaded())
56101
h.program.snapshot.affectedFilesPendingEmit.Range(func(path tspath.Path, emitKind FileEmitKind) bool {
57102
affectedFile := h.program.program.GetSourceFileByPath(path)
@@ -120,58 +165,42 @@ func (h *emitFilesHandler) emitAllAffectedFiles(options compiler.EmitOptions) *c
120165
return true
121166
})
122167

123-
results = h.updateSnapshot()
124-
125-
// Combine results and update buildInfo
126-
if h.isForDtsErrors && options.TargetSourceFile != nil {
127-
// Result from cache
128-
diagnostics, _ := h.program.snapshot.emitDiagnosticsPerFile.Load(options.TargetSourceFile.Path())
129-
return &compiler.EmitResult{
130-
EmitSkipped: true,
131-
Diagnostics: diagnostics.getDiagnostics(h.program.program, options.TargetSourceFile),
132-
}
133-
}
134-
135-
result := compiler.CombineEmitResults(results)
136-
if !h.isForDtsErrors {
137-
buildInfoResult := h.program.emitBuildInfo(h.ctx, options)
138-
if buildInfoResult != nil {
139-
result.Diagnostics = append(result.Diagnostics, buildInfoResult.Diagnostics...)
140-
result.EmittedFiles = append(result.EmittedFiles, buildInfoResult.EmittedFiles...)
141-
}
142-
}
143-
144-
return result
168+
return h.updateSnapshot()
145169
}
146170

147171
func (h *emitFilesHandler) getEmitOptions(options compiler.EmitOptions) compiler.EmitOptions {
148172
if !h.program.snapshot.options.GetEmitDeclarations() {
149173
return options
150174
}
175+
canUseIncrementalState := h.program.snapshot.canUseIncrementalState()
151176
return compiler.EmitOptions{
152177
TargetSourceFile: options.TargetSourceFile,
153178
EmitOnly: options.EmitOnly,
154179
WriteFile: func(fileName string, text string, writeByteOrderMark bool, data *compiler.WriteFileData) error {
155180
var differsOnlyInMap bool
156181
if tspath.IsDeclarationFileName(fileName) {
157-
var emitSignature string
158-
info, _ := h.program.snapshot.fileInfos.Load(options.TargetSourceFile.Path())
159-
if info.signature == info.version {
160-
signature := h.program.snapshot.computeSignatureWithDiagnostics(options.TargetSourceFile, text, data)
161-
// With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts
162-
if len(data.Diagnostics) == 0 {
163-
emitSignature = signature
182+
if canUseIncrementalState {
183+
var emitSignature string
184+
info, _ := h.program.snapshot.fileInfos.Load(options.TargetSourceFile.Path())
185+
if info.signature == info.version {
186+
signature := h.program.snapshot.computeSignatureWithDiagnostics(options.TargetSourceFile, text, data)
187+
// With d.ts diagnostics they are also part of the signature so emitSignature will be different from it since its just hash of d.ts
188+
if len(data.Diagnostics) == 0 {
189+
emitSignature = signature
190+
}
191+
if signature != info.version { // Update it
192+
h.signatures.Store(options.TargetSourceFile.Path(), signature)
193+
}
164194
}
165-
if signature != info.version { // Update it
166-
h.signatures.Store(options.TargetSourceFile.Path(), signature)
167-
}
168-
}
169195

170-
// Store d.ts emit hash so later can be compared to check if d.ts has changed.
171-
// Currently we do this only for composite projects since these are the only projects that can be referenced by other projects
172-
// and would need their d.ts change time in --build mode
173-
if h.skipDtsOutputOfComposite(options.TargetSourceFile, fileName, text, data, emitSignature, &differsOnlyInMap) {
174-
return nil
196+
// Store d.ts emit hash so later can be compared to check if d.ts has changed.
197+
// Currently we do this only for composite projects since these are the only projects that can be referenced by other projects
198+
// and would need their d.ts change time in --build mode
199+
if h.skipDtsOutputOfComposite(options.TargetSourceFile, fileName, text, data, emitSignature, &differsOnlyInMap) {
200+
return nil
201+
}
202+
} else if len(data.Diagnostics) > 0 {
203+
h.hasEmitDiagnostics.Store(true)
175204
}
176205
}
177206

@@ -231,56 +260,62 @@ func (h *emitFilesHandler) skipDtsOutputOfComposite(file *ast.SourceFile, output
231260
}
232261

233262
func (h *emitFilesHandler) updateSnapshot() []*compiler.EmitResult {
234-
h.signatures.Range(func(file tspath.Path, signature string) bool {
235-
info, _ := h.program.snapshot.fileInfos.Load(file)
236-
info.signature = signature
237-
if h.program.testingData != nil {
238-
h.program.testingData.UpdatedSignatureKinds[file] = SignatureUpdateKindStoredAtEmit
239-
}
240-
h.program.snapshot.buildInfoEmitPending.Store(true)
241-
return true
242-
})
243-
h.emitSignatures.Range(func(file tspath.Path, signature *emitSignature) bool {
244-
h.program.snapshot.emitSignatures.Store(file, signature)
245-
h.program.snapshot.buildInfoEmitPending.Store(true)
246-
return true
247-
})
248-
for file := range h.deletedPendingKinds.Keys() {
249-
h.program.snapshot.affectedFilesPendingEmit.Delete(file)
250-
h.program.snapshot.buildInfoEmitPending.Store(true)
251-
}
252-
// Always use correct order when to collect the result
253-
var results []*compiler.EmitResult
254-
for _, file := range h.program.GetSourceFiles() {
255-
if latestChangedDtsFile, ok := h.latestChangedDtsFiles.Load(file.Path()); ok {
256-
h.program.snapshot.latestChangedDtsFile = latestChangedDtsFile
263+
if h.program.snapshot.canUseIncrementalState() {
264+
h.signatures.Range(func(file tspath.Path, signature string) bool {
265+
info, _ := h.program.snapshot.fileInfos.Load(file)
266+
info.signature = signature
267+
if h.program.testingData != nil {
268+
h.program.testingData.UpdatedSignatureKinds[file] = SignatureUpdateKindStoredAtEmit
269+
}
270+
h.program.snapshot.buildInfoEmitPending.Store(true)
271+
return true
272+
})
273+
h.emitSignatures.Range(func(file tspath.Path, signature *emitSignature) bool {
274+
h.program.snapshot.emitSignatures.Store(file, signature)
275+
h.program.snapshot.buildInfoEmitPending.Store(true)
276+
return true
277+
})
278+
for file := range h.deletedPendingKinds.Keys() {
279+
h.program.snapshot.affectedFilesPendingEmit.Delete(file)
257280
h.program.snapshot.buildInfoEmitPending.Store(true)
258-
h.program.snapshot.hasChangedDtsFile = true
259281
}
260-
if update, ok := h.emitUpdates.Load(file.Path()); ok {
261-
if !update.dtsErrorsFromCache {
262-
if update.pendingKind == 0 {
263-
h.program.snapshot.affectedFilesPendingEmit.Delete(file.Path())
264-
} else {
265-
h.program.snapshot.affectedFilesPendingEmit.Store(file.Path(), update.pendingKind)
266-
}
282+
// Always use correct order when to collect the result
283+
var results []*compiler.EmitResult
284+
for _, file := range h.program.GetSourceFiles() {
285+
if latestChangedDtsFile, ok := h.latestChangedDtsFiles.Load(file.Path()); ok {
286+
h.program.snapshot.latestChangedDtsFile = latestChangedDtsFile
267287
h.program.snapshot.buildInfoEmitPending.Store(true)
288+
h.program.snapshot.hasChangedDtsFile = true
268289
}
269-
if update.result != nil {
270-
results = append(results, update.result)
271-
if len(update.result.Diagnostics) != 0 {
272-
h.program.snapshot.emitDiagnosticsPerFile.Store(file.Path(), &diagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: update.result.Diagnostics})
290+
if update, ok := h.emitUpdates.Load(file.Path()); ok {
291+
if !update.dtsErrorsFromCache {
292+
if update.pendingKind == 0 {
293+
h.program.snapshot.affectedFilesPendingEmit.Delete(file.Path())
294+
} else {
295+
h.program.snapshot.affectedFilesPendingEmit.Store(file.Path(), update.pendingKind)
296+
}
297+
h.program.snapshot.buildInfoEmitPending.Store(true)
298+
}
299+
if update.result != nil {
300+
results = append(results, update.result)
301+
if len(update.result.Diagnostics) != 0 {
302+
h.program.snapshot.emitDiagnosticsPerFile.Store(file.Path(), &diagnosticsOrBuildInfoDiagnosticsWithFileName{diagnostics: update.result.Diagnostics})
303+
}
273304
}
274305
}
275306
}
307+
return results
308+
} else if h.hasEmitDiagnostics.Load() {
309+
h.program.snapshot.hasEmitDiagnostics = true
276310
}
277-
return results
311+
return nil
278312
}
279313

280314
func emitFiles(ctx context.Context, program *Program, options compiler.EmitOptions, isForDtsErrors bool) *compiler.EmitResult {
281315
emitHandler := &emitFilesHandler{ctx: ctx, program: program, isForDtsErrors: isForDtsErrors}
282316

283-
if options.TargetSourceFile != nil {
317+
// Single file emit - do direct from program
318+
if !isForDtsErrors && options.TargetSourceFile != nil {
284319
result := program.program.Emit(ctx, emitHandler.getEmitOptions(options))
285320
if ctx.Err() != nil {
286321
return nil

0 commit comments

Comments
 (0)