mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Emit a debug build's function bodies in one emitter
Debug metadata is numbered per emitter (debug-info.ts) and the range journal does not carry it, so a debug build emitted in ranges or on workers referred to metadata the merged module did not define. Debug builds now emit every function body in the emitter itself.
This commit is contained in:
@@ -192,10 +192,13 @@ function readRange(dir: string, range: number): { result: ChunkResult; texts: st
|
||||
export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
// Function bodies first (the literal/unit/fn-value tables fill as they
|
||||
// emit), then the file assembles around them — the runtime ABI’s order.
|
||||
// Debug metadata is numbered per emitter and not journaled, so a debug
|
||||
// build emits every body here.
|
||||
const ranges = emitter.debug === null;
|
||||
const chunks = emitter.targetOptions.functionChunks ?? 1;
|
||||
const fnDefs =
|
||||
(chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ??
|
||||
emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) ??
|
||||
(ranges && chunks > 1 ? emitFunctionsInChunks(emitter, chunks) : null) ??
|
||||
(ranges ? emitFunctionsOnWorkers(emitter, emitter.targetOptions.functionWorkers ?? 1) : null) ??
|
||||
[];
|
||||
if (fnDefs.length === 0)
|
||||
for (const fn of emitter.mod.functions) {
|
||||
|
||||
@@ -414,11 +414,13 @@ export interface LlvmTargetOptions {
|
||||
targetTriple?: string;
|
||||
/** Emit the function bodies in this many contiguous ranges, each by its
|
||||
* own emitter, and merge them in function order (emit-journal.ts). The
|
||||
* output is the same as one emitter's. */
|
||||
* output is the same as one emitter's. Debug builds (`debugSources`)
|
||||
* emit in one emitter. */
|
||||
functionChunks?: number;
|
||||
/** Emit the function bodies of a large module on this many forked worker
|
||||
* processes at a time (emit-module.ts), forked with `forkPrimitives`. The
|
||||
* output is the same as one emitter's. */
|
||||
* output is the same as one emitter's. Debug builds (`debugSources`)
|
||||
* emit in one emitter. */
|
||||
functionWorkers?: number;
|
||||
/** The host's process primitives, where it can fork (the native
|
||||
* compiler's). */
|
||||
@@ -1273,7 +1275,9 @@ export class LlEmitter {
|
||||
this.journal = journal;
|
||||
this.journaled =
|
||||
journal !== null ||
|
||||
((options.functionWorkers ?? 1) > 1 && options.forkPrimitives !== undefined);
|
||||
((options.functionWorkers ?? 1) > 1 &&
|
||||
options.forkPrimitives !== undefined &&
|
||||
options.debugSources === undefined);
|
||||
this.stackTraces = observesStacks(mod);
|
||||
this.nullableUnions = new NullableUnions(mod, options.debugSources === undefined);
|
||||
this.mockTimers = this.runtimeFeatures.mockTimers;
|
||||
|
||||
@@ -67,3 +67,56 @@ test("function bodies emitted in chunks merge into the text one emitter gives",
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}, 600_000);
|
||||
|
||||
// Debug metadata is numbered per emitter (debug-info.ts) and not journaled:
|
||||
// a debug build emits its function bodies in one emitter.
|
||||
test("debug builds emit function bodies serially, with every metadata reference defined", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-chunked-debug-"));
|
||||
const minFunctions = process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"];
|
||||
try {
|
||||
const corpus = join(import.meta.dirname, "../../../tests/corpus/3066-source-debugging");
|
||||
const entry = join(corpus, "main.ts");
|
||||
const outPath = join(dir, "main.ir.json");
|
||||
const result = await compile(entry, {
|
||||
outDir: dir,
|
||||
outPath,
|
||||
outputKind: "ir",
|
||||
optimization: "dev",
|
||||
});
|
||||
if (!result.ok)
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
const ir = await readFile(outPath, "utf8");
|
||||
const debugSources = new Map<string, string>();
|
||||
for (const name of ["main.ts", "helper.ts"])
|
||||
debugSources.set(join(corpus, name), await readFile(join(corpus, name), "utf8"));
|
||||
const options: LlvmTargetOptions = { debugSources };
|
||||
const serial = text(emitLlvmModuleSource(deserializeModule(ir), options));
|
||||
expect(serial).toContain("!DILocation");
|
||||
|
||||
let forks = 0;
|
||||
process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = "1";
|
||||
const forkPrimitives = {
|
||||
fork: (): number => {
|
||||
forks++;
|
||||
return -1;
|
||||
},
|
||||
exit: (): void => {},
|
||||
wait: (): number => -1,
|
||||
};
|
||||
for (const variant of [
|
||||
{ functionChunks: 3 },
|
||||
{ functionWorkers: 4, forkPrimitives },
|
||||
] satisfies LlvmTargetOptions[]) {
|
||||
const emitted = text(emitLlvmModuleSource(deserializeModule(ir), { ...options, ...variant }));
|
||||
expect(emitted === serial, JSON.stringify(Object.keys(variant))).toBe(true);
|
||||
const defined = new Set([...emitted.matchAll(/^(![0-9]+) = /gm)].map((m) => m[1]));
|
||||
const referenced = new Set([...emitted.matchAll(/(![0-9]+)\b/g)].map((m) => m[1]));
|
||||
expect([...referenced].filter((id) => !defined.has(id))).toEqual([]);
|
||||
}
|
||||
expect(forks).toBe(0);
|
||||
} finally {
|
||||
if (minFunctions === undefined) delete process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"];
|
||||
else process.env["SCRIPTC_EMIT_WORKER_FUNCTIONS"] = minFunctions;
|
||||
await rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
}, 120_000);
|
||||
|
||||
Reference in New Issue
Block a user