vendor tsgo
This commit is contained in:
167
tools/tsgo/internal/compiler/checkerpool.go
Normal file
167
tools/tsgo/internal/compiler/checkerpool.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/ast"
|
||||
"github.com/microsoft/typescript-go/internal/checker"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/tracing"
|
||||
)
|
||||
|
||||
// CheckerPool is implemented by the project system to provide checkers with
|
||||
// request-scoped lifetime and reclamation. It returns a checker and a release
|
||||
// function that must be called when the caller is done with the checker.
|
||||
// The returned checker must not be accessed concurrently; each acquisition is exclusive.
|
||||
// If file is non-nil, the pool may use it as an affinity hint to return the same
|
||||
// checker for the same file across calls.
|
||||
type CheckerPool interface {
|
||||
GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func())
|
||||
}
|
||||
|
||||
type checkerPool struct {
|
||||
program *Program
|
||||
tracing *tracing.Tracing
|
||||
|
||||
createCheckersOnce sync.Once
|
||||
checkers []*checker.Checker
|
||||
locks []*sync.Mutex
|
||||
fileAssociations map[*ast.SourceFile]*checker.Checker
|
||||
}
|
||||
|
||||
var _ CheckerPool = (*checkerPool)(nil)
|
||||
|
||||
func newCheckerPool(program *Program) *checkerPool {
|
||||
return newCheckerPoolWithTracing(program, nil)
|
||||
}
|
||||
|
||||
func newCheckerPoolWithTracing(program *Program, tr *tracing.Tracing) *checkerPool {
|
||||
checkerCount := 4
|
||||
if program.SingleThreaded() {
|
||||
checkerCount = 1
|
||||
} else if c := program.Options().Checkers; c != nil {
|
||||
checkerCount = *c
|
||||
}
|
||||
|
||||
checkerCount = max(min(checkerCount, len(program.files), 256), 1)
|
||||
|
||||
pool := &checkerPool{
|
||||
program: program,
|
||||
checkers: make([]*checker.Checker, checkerCount),
|
||||
locks: make([]*sync.Mutex, checkerCount),
|
||||
tracing: tr,
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// GetChecker implements CheckerPool. When file is non-nil, returns the checker
|
||||
// associated with that file; otherwise returns the first checker.
|
||||
func (p *checkerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
if file != nil {
|
||||
return p.getCheckerForFileExclusive(ctx, file)
|
||||
}
|
||||
p.createCheckers()
|
||||
c := p.checkers[0]
|
||||
p.locks[0].Lock()
|
||||
return c, sync.OnceFunc(func() {
|
||||
p.locks[0].Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// getCheckerForFileNonExclusive returns the checker for the given file without locking.
|
||||
// This is only safe when the caller guarantees no concurrent access to the same checker,
|
||||
// e.g. for read-only operations like obtaining an emit resolver.
|
||||
func (p *checkerPool) getCheckerForFileNonExclusive(file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
return p.fileAssociations[file], noop
|
||||
}
|
||||
|
||||
func (p *checkerPool) getCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
c := p.fileAssociations[file]
|
||||
idx := slices.Index(p.checkers, c)
|
||||
p.locks[idx].Lock()
|
||||
return c, sync.OnceFunc(func() {
|
||||
p.locks[idx].Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
// getCheckerNonExclusive returns the first checker without locking.
|
||||
func (p *checkerPool) getCheckerNonExclusive() (*checker.Checker, func()) {
|
||||
p.createCheckers()
|
||||
return p.checkers[0], noop
|
||||
}
|
||||
|
||||
func (p *checkerPool) createCheckers() {
|
||||
p.createCheckersOnce.Do(func() {
|
||||
checkerCount := len(p.checkers)
|
||||
wg := core.NewWorkGroup(p.program.SingleThreaded())
|
||||
for i := range checkerCount {
|
||||
wg.Queue(func() {
|
||||
var tracer *checker.Tracer
|
||||
if p.tracing != nil {
|
||||
tracer = checker.NewTracer(p.tracing, i)
|
||||
}
|
||||
p.checkers[i], p.locks[i] = checker.NewChecker(p.program, tracer)
|
||||
})
|
||||
}
|
||||
|
||||
wg.RunAndWait()
|
||||
|
||||
p.fileAssociations = make(map[*ast.SourceFile]*checker.Checker, len(p.program.files))
|
||||
for i, file := range p.program.files {
|
||||
p.fileAssociations[file] = p.checkers[i%checkerCount]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Runs `cb` for each checker in the pool concurrently, locking and unlocking checker mutexes as it goes,
|
||||
// making it safe to call `forEachCheckerParallel` from many threads simultaneously.
|
||||
func (p *checkerPool) forEachCheckerParallel(cb func(idx int, c *checker.Checker)) {
|
||||
p.createCheckers()
|
||||
wg := core.NewWorkGroup(p.program.SingleThreaded())
|
||||
for idx, checker := range p.checkers {
|
||||
wg.Queue(func() {
|
||||
p.locks[idx].Lock()
|
||||
defer p.locks[idx].Unlock()
|
||||
cb(idx, checker)
|
||||
})
|
||||
}
|
||||
wg.RunAndWait()
|
||||
}
|
||||
|
||||
func (p *checkerPool) GetGlobalDiagnostics() []*ast.Diagnostic {
|
||||
p.createCheckers()
|
||||
globalDiagnostics := make([][]*ast.Diagnostic, len(p.checkers))
|
||||
p.forEachCheckerParallel(func(idx int, checker *checker.Checker) {
|
||||
globalDiagnostics[idx] = checker.GetGlobalDiagnostics()
|
||||
})
|
||||
return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...))
|
||||
}
|
||||
|
||||
// forEachCheckerGroupDo runs one task per checker in parallel. Each task iterates
|
||||
// the provided files, processing only those assigned to its checker. Within each
|
||||
// checker's set, files are visited in their original order.
|
||||
func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.SourceFile, singleThreaded bool, cb func(c *checker.Checker, fileIndex int, file *ast.SourceFile)) {
|
||||
p.createCheckers()
|
||||
|
||||
checkerCount := len(p.checkers)
|
||||
wg := core.NewWorkGroup(singleThreaded)
|
||||
for checkerIdx := range checkerCount {
|
||||
wg.Queue(func() {
|
||||
p.locks[checkerIdx].Lock()
|
||||
defer p.locks[checkerIdx].Unlock()
|
||||
for i, file := range files {
|
||||
if checker := p.checkers[checkerIdx]; checker == p.fileAssociations[file] {
|
||||
cb(checker, i, file)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.RunAndWait()
|
||||
}
|
||||
|
||||
func noop() {}
|
||||
Reference in New Issue
Block a user