diff --git a/packages/compiler/src/native/linker-job.test.ts b/packages/compiler/src/native/linker-job.test.ts index b9d718c03..96d186baf 100644 --- a/packages/compiler/src/native/linker-job.test.ts +++ b/packages/compiler/src/native/linker-job.test.ts @@ -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) => [...s.args, "-v", "-Wl,-t"]; + const linkThroughDriver = (s: ReturnType) => { + 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(); diff --git a/packages/compiler/src/native/linker-job.ts b/packages/compiler/src/native/linker-job.ts index e1195e4c4..f006cb233 100644 --- a/packages/compiler/src/native/linker-job.ts +++ b/packages/compiler/src/native/linker-job.ts @@ -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. */