Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tsc/internal/compiler/checkerpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,16 @@ func (p *checkerPool) forEachCheckerGroupDo(ctx context.Context, files []*ast.So
wg.RunAndWait()
}

// groupFilesByChecker returns the indices of files grouped by the checker each file is associated with. Files keep
// their order within each group.
func (p *checkerPool) groupFilesByChecker(files []*ast.SourceFile) [][]int {
p.createCheckers()
groups := make([][]int, len(p.checkers))
for i, file := range files {
idx := slices.Index(p.checkers, p.fileAssociations[file])
groups[idx] = append(groups[idx], i)
}
return groups
}

func noop() {}
25 changes: 22 additions & 3 deletions tsc/internal/compiler/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,31 @@ type emitter struct {
forceEmit bool
writeFile WriteFile
tr *tracing.Tracing
prints []func()
}

func (e *emitter) emit() {
// transform runs the JS and declaration transformers for the file, the part of emit that uses the checker. print
// prints and writes what transform produced.
func (e *emitter) transform() {
if e.tr != nil {
defer e.tr.Push(tracing.PhaseEmit, "emit", map[string]any{"path": string(e.sourceFile.PathKey())}, true)()
}
emitContext := printer.NewEmitContext()
emitResolver := e.host.NewEmitResolver(emitContext)
e.emitJSFile(emitResolver, e.sourceFile, e.paths.JsFilePath(), e.paths.SourceMapFilePath())
e.emitDeclarationFile(emitResolver, e.sourceFile, e.paths.DeclarationFilePath(), e.paths.DeclarationMapPath())
}

// print prints and writes the outputs transformed by transform. It must not use the checker, which may be in use by
// another file by then.
func (e *emitter) print() {
if e.tr != nil {
defer e.tr.Push(tracing.PhaseEmit, "emitPrint", map[string]any{"path": string(e.sourceFile.PathKey())}, true)()
}
for _, print := range e.prints {
print()
}
e.prints = nil
e.emitResult.Diagnostics = e.emitterDiagnostics.GetDiagnostics()
}

Expand Down Expand Up @@ -219,7 +234,9 @@ func (e *emitter) emitJSFile(emitResolver printer.EmitResolver, sourceFile *ast.
// !!!
}, emitContext)

e.printSourceFile(emitContext, jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile))
e.prints = append(e.prints, func() {
e.printSourceFile(emitContext, jsFilePath, sourceMapFilePath, sourceFile, printer, options, shouldEmitSourceMaps(options, sourceFile))
})
}

func (e *emitter) emitDeclarationFile(emitResolver printer.EmitResolver, sourceFile *ast.SourceFile, declarationFilePath tspath.RootedFilePath, declarationMapPath tspath.RootedFilePath) {
Expand Down Expand Up @@ -292,7 +309,9 @@ func (e *emitter) emitDeclarationFile(emitResolver printer.EmitResolver, sourceF
MapRoot: options.MapRoot,
// Explicitly do not pass through either inline option.
}
e.printSourceFile(emitContext, declarationFilePath, declarationMapPath, sourceFile, printer, declarationMapOptions, shouldEmitSourceMaps(declarationMapOptions, sourceFile))
e.prints = append(e.prints, func() {
e.printSourceFile(emitContext, declarationFilePath, declarationMapPath, sourceFile, printer, declarationMapOptions, shouldEmitSourceMaps(declarationMapOptions, sourceFile))
})
}

type declarationMapSource struct {
Expand Down
77 changes: 54 additions & 23 deletions tsc/internal/compiler/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -1933,37 +1933,68 @@ func (p *Program) Emit(ctx context.Context, options EmitOptions) *EmitResult {
sourceFiles := p.getSourceFilesToEmit(options.TargetSourceFiles, forceDtsEmit, forceJsEmit)

for _, sourceFile := range sourceFiles {
emitter := &emitter{
emitters = append(emitters, &emitter{
writer: nil,
sourceFile: sourceFile,
emitOnly: options.EmitOnly,
forceEmit: options.ForceEmit,
writeFile: options.WriteFile,
tr: p.hosts.Tracing,
})
}
transformFile := func(emitter *emitter) {
sourceFile := emitter.sourceFile
host, done := newEmitHost(ctx, p, sourceFile)
defer done()
emitter.host = host
emitter.paths = outputpaths.GetOutputPathsFor(sourceFile, host.Options(), host, outputpaths.ForceEmitPaths{
Dts: forceDtsEmit,
Js: forceJsEmit,
DeclarationMap: options.ForceEmit && options.EmitOnly == EmitOnlyDts,
})
emitter.transform()
}
printFile := func(emitter *emitter) {
// take an unused writer
writer := writerPool.Get().(printer.EmitTextWriter)
writer.Clear()

// attach writer and print
emitter.writer = writer
emitter.print()
emitter.writer = nil

// put the writer back in the pool
writerPool.Put(writer)
}
if p.compilerCheckerPool != nil && !p.SingleThreaded() {
// Transform the files of each checker in one task. Transforming can create types and resolve symbols, so a
// checker shared by concurrent tasks would do that work in an order that depends on scheduling. Files are
// transformed in reverse program order, the order in which a single-threaded work group runs per-file tasks,
// so that a program with one checker produces the same output whether or not it is single-threaded. Printing
// does not use the checker, so it runs in tasks of its own. Single-threaded programs emit file by file instead,
// since queued prints would only run after every file is transformed.
for _, group := range p.compilerCheckerPool.groupFilesByChecker(sourceFiles) {
if len(group) == 0 {
continue
}
wg.Queue(func() {
for _, i := range slices.Backward(group) {
emitter := emitters[i]
transformFile(emitter)
wg.Queue(func() {
printFile(emitter)
})
}
})
}
emitters = append(emitters, emitter)
wg.Queue(func() {
host, done := newEmitHost(ctx, p, sourceFile)
defer done()
emitter.host = host

// take an unused writer
writer := writerPool.Get().(printer.EmitTextWriter)
writer.Clear()

// attach writer and perform emit
emitter.writer = writer
emitter.paths = outputpaths.GetOutputPathsFor(sourceFile, host.Options(), host, outputpaths.ForceEmitPaths{
Dts: forceDtsEmit,
Js: forceJsEmit,
DeclarationMap: options.ForceEmit && options.EmitOnly == EmitOnlyDts,
} else {
for _, emitter := range emitters {
wg.Queue(func() {
transformFile(emitter)
printFile(emitter)
})
emitter.emit()
emitter.writer = nil

// put the writer back in the pool
writerPool.Put(writer)
})
}
}

// wait for emit to complete
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//// [tests/cases/compiler/noFlakyDeclarationEmitSharedCheckerOrder.ts] ////

//// [ma.js]
export { v as pa } from "./xa";
export const ma = 1;

//// [mb.js]
export { v as pb } from "./xb";
export const mb = 1;

//// [xa.js]
/** @type {typeof import("./mb")} */
const M = /** @type {any} */ (null);
const o = { v: { ...M } };
module.exports = o;

//// [xb.js]
/** @type {typeof import("./ma")} */
const M = /** @type {any} */ (null);
const o = { v: { ...M } };
module.exports = o;

//// [a.ts]
import * as MA from "./ma";
export const fromA = MA.pa;

//// [b.ts]
import * as MB from "./mb";
export const fromB = MB.pb;

//// [c.ts]
export const c = 1;


//// [xb.js]
"use strict";
/** @type {typeof import("./ma")} */
const M = /** @type {any} */ (null);
const o = { v: { ...M } };
module.exports = o;
//// [mb.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mb = exports.pb = void 0;
var xb_1 = require("./xb");
Object.defineProperty(exports, "pb", { enumerable: true, get: function () { return xb_1.v; } });
exports.mb = 1;
//// [xa.js]
"use strict";
/** @type {typeof import("./mb")} */
const M = /** @type {any} */ (null);
const o = { v: { ...M } };
module.exports = o;
//// [ma.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ma = exports.pa = void 0;
var xa_1 = require("./xa");
Object.defineProperty(exports, "pa", { enumerable: true, get: function () { return xa_1.v; } });
exports.ma = 1;
//// [a.js]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromA = void 0;
const MA = __importStar(require("./ma"));
exports.fromA = MA.pa;
//// [b.js]
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromB = void 0;
const MB = __importStar(require("./mb"));
exports.fromB = MB.pb;
//// [c.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.c = void 0;
exports.c = 1;


//// [xb.d.ts]
export = o;
declare const o: {
v: {
pa: {};
ma: 1;
};
};
//// [mb.d.ts]
export { v as pb } from "./xb";
export declare const mb = 1;
//// [xa.d.ts]
export = o;
declare const o: {
v: {};
};
//// [ma.d.ts]
export { v as pa } from "./xa";
export declare const ma = 1;
//// [a.d.ts]
export declare const fromA: {};
//// [b.d.ts]
export declare const fromB: {
pa: {};
ma: 1;
};
//// [c.d.ts]
export declare const c = 1;
Loading