mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
The Linux helpers linked LLVM 22.1.8's release archive. The helper's code generation is LLVM's code, so building that LLVM from its release sources with ThinLTO and with a profile of the helper's own work makes code generation 5-9% faster than the helper as released, with identical objects. - native/llvm-codegen/llvm.profdata.gz is the profile, collected by scripts/refresh-llvm-profile.mjs: it builds LLVM with IR instrumentation and an instrumented helper, and runs that helper on tsc-ts, five Effect fixtures, the runtime benchmark workloads and every 50th corpus program (scripts/llvm-helper-inputs.mjs). It follows LLVM's sources, so it needs a refresh when LLVM is bumped. - scripts/build-llvm-dist.mjs builds LLVM with that profile and ThinLTO, and packs it with a checksum. The llvm-dist workflow, run by hand, builds it for x64 and arm64, requires a helper linked against it to give the same objects as one linked against the release archive (scripts/check-llvm-dist.mjs), prints both helpers' times and the profile's mismatch count, and attaches the archive to the LLVM_DIST_TAG release of this repository. - The release workflow's Linux jobs, and the wasm32-wasi job, which also uploads the x64 GNU helper, download that archive and build the helper with SCRIPTC_LLVM_THINLTO=1: clang, lld and ThinLTO, and the profile for the helper's own sources. Without the archive, or if that build fails, the helper links the release archive as before. - The four Linux helper packages share their build in native/llvm-codegen/linux-helper.mjs. Without SCRIPTC_LLVM_THINLTO it builds the same executable as before.
106 lines
4.3 KiB
JavaScript
106 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/* Compares two LLVM helpers on the helper inputs of this checkout
|
|
* (scripts/llvm-helper-inputs.mjs): every input must give identical objects
|
|
* with both, and the script prints each input group's median wall time per
|
|
* helper over the rounds, with the candidate/baseline ratio. The llvm-dist
|
|
* workflow runs it with the helper linked against LLVM's release archive as
|
|
* the baseline and the one linked against the profiled ThinLTO distribution
|
|
* as the candidate, on each architecture it builds. Exits with status 1 when
|
|
* an object differs.
|
|
*
|
|
* node scripts/check-llvm-dist.mjs --baseline <helper> --candidate <helper>
|
|
* [--rounds N] [--work <dir>] [--tsc-ts <checkout>] [--extra <dir of .ts programs>]
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
import { performance } from "node:perf_hooks";
|
|
import { parseArgs } from "node:util";
|
|
import { captureHelperInputs, helperInputPrograms } from "./llvm-helper-inputs.mjs";
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
baseline: { type: "string" },
|
|
candidate: { type: "string" },
|
|
rounds: { type: "string", default: "3" },
|
|
work: { type: "string" },
|
|
"tsc-ts": { type: "string" },
|
|
extra: { type: "string", multiple: true, default: [] },
|
|
help: { type: "boolean", default: false },
|
|
},
|
|
});
|
|
if (values.help || values.baseline === undefined || values.candidate === undefined) {
|
|
console.log(
|
|
"Usage: node scripts/check-llvm-dist.mjs --baseline <helper> --candidate <helper> [--rounds N] [--work <dir>] [--tsc-ts <checkout>] [--extra <dir>]",
|
|
);
|
|
process.exit(values.help ? 0 : 2);
|
|
}
|
|
const helpers = { baseline: resolve(values.baseline), candidate: resolve(values.candidate) };
|
|
const work = resolve(values.work ?? mkdtempSync(join(tmpdir(), "scriptc-llvm-check-")));
|
|
const inputs = await captureHelperInputs(
|
|
helperInputPrograms({
|
|
tscTs: values["tsc-ts"] === undefined ? undefined : resolve(values["tsc-ts"]),
|
|
extra: values.extra.map((dir) => resolve(dir)),
|
|
}),
|
|
join(work, "inputs"),
|
|
);
|
|
|
|
function emit(helper, input, out) {
|
|
rmSync(out, { recursive: true, force: true });
|
|
mkdirSync(out, { recursive: true });
|
|
const start = performance.now();
|
|
const result = spawnSync(helper, input.args(out), { encoding: "utf8" });
|
|
const ms = performance.now() - start;
|
|
if (result.status !== 0)
|
|
throw new Error(`${helper} failed on ${input.name}: ${result.stdout}${result.stderr}`);
|
|
return ms;
|
|
}
|
|
|
|
const times = new Map(); // `${group}|${helper}` -> per-round totals
|
|
const group = (name) => (name === "tsc-ts" ? name : name.split("-")[0]);
|
|
const rounds = Number(values.rounds);
|
|
let differing = 0;
|
|
for (let round = 0; round < rounds; round++) {
|
|
const order = round % 2 === 0 ? ["baseline", "candidate"] : ["candidate", "baseline"];
|
|
for (const input of inputs) {
|
|
for (const which of order) {
|
|
const ms = emit(helpers[which], input, join(work, which));
|
|
const key = `${group(input.name)}|${which}`;
|
|
const totals = times.get(key) ?? new Array(rounds).fill(0);
|
|
totals[round] += ms;
|
|
times.set(key, totals);
|
|
}
|
|
if (round === 0) {
|
|
for (const file of readdirSync(join(work, "baseline"))) {
|
|
const a = readFileSync(join(work, "baseline", file));
|
|
const b = readFileSync(join(work, "candidate", file));
|
|
if (!a.equals(b)) {
|
|
differing++;
|
|
console.log(`${input.name}: ${file} differs between the helpers`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const median = (xs) => {
|
|
const s = [...xs].sort((x, y) => x - y);
|
|
return s.length % 2 ? s[s.length >> 1] : (s[s.length / 2 - 1] + s[s.length / 2]) / 2;
|
|
};
|
|
console.log(`${inputs.length} inputs, ${rounds} rounds; median wall time per group`);
|
|
console.log("| group | baseline | candidate | candidate/baseline |");
|
|
console.log("| --- | ---: | ---: | ---: |");
|
|
for (const name of [...new Set(inputs.map((input) => group(input.name)))]) {
|
|
const b = median(times.get(`${name}|baseline`)),
|
|
c = median(times.get(`${name}|candidate`));
|
|
console.log(
|
|
`| ${name} | ${(b / 1000).toFixed(2)} s | ${(c / 1000).toFixed(2)} s | ${(c / b).toFixed(3)} |`,
|
|
);
|
|
}
|
|
if (differing > 0) {
|
|
console.log(`${differing} objects differ between the helpers`);
|
|
process.exit(1);
|
|
}
|
|
console.log("every object is identical between the helpers");
|