Record a linker job again when a path it names changes (#2428)

This commit is contained in:
Chris Tate
2026-10-27 12:58:01 -05:00
committed by GitHub
2 changed files with 50 additions and 9 deletions
@@ -212,7 +212,7 @@ test.skipIf(process.platform === "win32")(
);
test.skipIf(process.platform === "win32")(
"a changed startup object, library directory or linker sends the link to the driver",
"a changed startup object, library directory or linker records the job again",
() => {
for (const change of ["crt", "lib", "ld"] as const) {
const f = fixture();
@@ -230,12 +230,44 @@ test.skipIf(process.platform === "win32")(
}
const second = f.stage();
const count = f.calls().length;
expect(open().run(second.args, second.paths, second.directory, true)).toBeNull();
expect(f.calls().length).toBe(count);
expect(open().run(second.args, second.paths, second.directory, true)).not.toBeNull();
expect(f.calls().slice(count)).toEqual([
`driver ${second.args.join(" ")} -v -###`,
`ld.lld -pie ${f.crt} -L${f.lib} -pthread ${second.paths[0]} -lm -o ${second.output}`,
]);
}
},
);
test.skipIf(process.platform === "win32")(
"a traced link whose recorded job names a changed path records it again",
() => {
const f = fixture();
const open = () => LinkerJobs.open(f.cache, f.toolchain(), ["-fuse-ld=lld"], false)!;
const traced = (s: ReturnType<typeof f.stage>) => [...s.args, "-v", "-Wl,-t"];
const linkThroughDriver = (s: ReturnType<typeof f.stage>) => {
const link = open().link(s.paths, s.directory);
expect(link.run(traced(s), false)).toBeNull();
const linked = spawnSync(f.driver, traced(s), { encoding: "utf8" });
expect(linked.status).toBe(0);
link.record(traced(s), linked.stdout + "\n" + linked.stderr);
};
linkThroughDriver(f.stage());
const before = statSync(f.crt);
writeFileSync(f.crt, "new startup object");
utimesSync(f.crt, before.atime, new Date(before.mtimeMs + 1000));
// The recorded job names the old startup object: the link runs through the driver once,
// and its job is recorded again.
linkThroughDriver(f.stage());
const third = f.stage();
const count = f.calls().length;
expect(open().link(third.paths, third.directory).run(traced(third), false)).not.toBeNull();
expect(f.calls().slice(count)).toEqual([
`ld.lld -pie ${f.crt} -L${f.lib} -pthread ${third.paths[0]} -lm -o ${third.output} -t`,
]);
},
);
test.skipIf(process.platform === "win32")("a changed driver records the job again", () => {
const f = fixture();
const first = f.stage();
+15 -6
View File
@@ -164,10 +164,11 @@ export class LinkerJobs {
* Runs the link that the driver arguments `args` describe as its recorded
* job. `paths` are the link's own inputs and output among `args`, which may
* differ from build to build; `stage` is the directory they are in. Without
* an entry, `list` records the job first with the driver's `-v -###`;
* an entry, or with one whose job names a path that changed since it was
* recorded, `list` records the job first with the driver's `-v -###`;
* otherwise the caller links through the driver and passes its `-v` output
* to record(). Null when the link has to run through the driver: no job is
* recorded, a recorded path changed, or the job failed.
* to record(), which records it again. Null when the link has to run
* through the driver: no job is recorded, or the job failed.
*/
run(
args: readonly string[],
@@ -180,7 +181,7 @@ export class LinkerJobs {
const roots = [resolve(stage), realpathSync(stage)];
const key = this.key(args, paths, roots);
if (key === null) return null;
let entry = this.read(key, paths.length);
let entry = this.fresh(this.read(key, paths.length));
if (entry === null && list) {
const result = spawnSync(this.driver, [...args, "-v", "-###"], {
encoding: "utf8",
@@ -190,7 +191,7 @@ export class LinkerJobs {
return null;
entry = this.store(key, result.stdout + "\n" + result.stderr, paths, roots);
}
if (entry === null || entry.job === null || !stillMatches(entry.inputs)) return null;
if (entry === null || entry.job === null) return null;
const job = entry.job.map((token) => (typeof token === "number" ? paths[token]! : token));
const result = spawnSync(job[0]!, job.slice(1), {
encoding: "utf8",
@@ -206,6 +207,14 @@ export class LinkerJobs {
}
}
/** `entry`, or null when a path its job names changed since it was
* recorded: the next link through the driver records the job again. An
* entry without a job, a link the driver cannot hand to one ld.lld job,
* stays. */
private fresh(entry: LinkerJobEntry | null): LinkerJobEntry | null {
return entry !== null && entry.job !== null && !stillMatches(entry.inputs) ? null : entry;
}
private read(key: string, paths: number): LinkerJobEntry | null {
const bytes = this.cache.read("linker-job", key, MAX_ENTRY_BYTES);
if (bytes === null) return null;
@@ -231,7 +240,7 @@ export class LinkerJobs {
try {
const roots = [resolve(stage), realpathSync(stage)];
const key = this.key(args, paths, roots);
if (key !== null && this.read(key, paths.length) === null)
if (key !== null && this.fresh(this.read(key, paths.length)) === null)
this.store(key, output, paths, roots, driverInputs);
} catch {
/* The next link records it. */