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.
113 lines
4.7 KiB
JavaScript
113 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/* Refreshes native/llvm-codegen/llvm.profdata.gz, the profile the release
|
|
* LLVM distribution and the Linux helpers' own sources are optimized with
|
|
* (native/llvm-codegen/llvm-dist.mjs). The profile follows LLVM's sources
|
|
* and the kind of code scriptc emits rather than scriptc's commits, so it
|
|
* needs a refresh when LLVM is bumped, or when the emitted code changes
|
|
* enough that the distribution check (scripts/check-llvm-dist.mjs) loses
|
|
* much of its gain.
|
|
*
|
|
* Builds LLVM with IR instrumentation (scripts/build-llvm-dist.mjs
|
|
* --instrumented) and an instrumented helper against it, then runs that
|
|
* helper on the training inputs (scripts/llvm-helper-inputs.mjs): tsc-ts,
|
|
* five Effect fixtures, the runtime benchmark workloads and every 50th
|
|
* corpus program. The merged profile is written gzip-compressed. Needs a
|
|
* built checkout on Linux x64, clang, lld and llvm-profdata of LLVM_VERSION
|
|
* on PATH, and the LLVM release sources. About 20 minutes on 32 cores.
|
|
*
|
|
* node scripts/refresh-llvm-profile.mjs --source <llvm-project-X.src>
|
|
* --tsc-ts <tsc-ts checkout> [--work <dir>] [--jobs N] [--output <profdata.gz>]
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { cpus, tmpdir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parseArgs } from "node:util";
|
|
import { constants, gzipSync } from "node:zlib";
|
|
import { llvmProfile } from "../native/llvm-codegen/llvm-dist.mjs";
|
|
import { buildLinuxHelper } from "../native/llvm-codegen/linux-helper.mjs";
|
|
import { captureHelperInputs, helperInputPrograms } from "./llvm-helper-inputs.mjs";
|
|
|
|
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
const { values } = parseArgs({
|
|
options: {
|
|
source: { type: "string" },
|
|
"tsc-ts": { type: "string" },
|
|
work: { type: "string" },
|
|
jobs: { type: "string", default: String(cpus().length) },
|
|
output: { type: "string", default: llvmProfile },
|
|
help: { type: "boolean", default: false },
|
|
},
|
|
});
|
|
if (values.help || values.source === undefined || values["tsc-ts"] === undefined) {
|
|
console.log(
|
|
"Usage: node scripts/refresh-llvm-profile.mjs --source <llvm-project source> --tsc-ts <tsc-ts checkout> [--work <dir>] [--jobs N] [--output <profdata.gz>]",
|
|
);
|
|
process.exit(values.help ? 0 : 2);
|
|
}
|
|
if (process.platform !== "linux" || process.arch !== "x64")
|
|
throw new Error("the LLVM profile is collected on Linux x64");
|
|
const work = resolve(values.work ?? mkdtempSync(join(tmpdir(), "scriptc-llvm-profile-")));
|
|
const prefix = join(work, "llvm-instrumented-install");
|
|
const helper = join(work, "helper", "scriptc-llvm-codegen");
|
|
const raw = join(work, "raw");
|
|
|
|
function run(command, args, options = {}) {
|
|
const result = spawnSync(command, args, { stdio: "inherit", ...options });
|
|
if (result.status !== 0)
|
|
throw new Error(`${command} ${args[0]} failed with status ${result.status}`);
|
|
}
|
|
|
|
console.log(`building the instrumented LLVM in ${work}`);
|
|
run(process.execPath, [
|
|
join(root, "scripts/build-llvm-dist.mjs"),
|
|
"--instrumented",
|
|
"--source",
|
|
resolve(values.source),
|
|
"--prefix",
|
|
prefix,
|
|
"--work",
|
|
work,
|
|
"--jobs",
|
|
values.jobs,
|
|
]);
|
|
const manifest = JSON.parse(
|
|
readFileSync(join(root, "packages/llvm-linux-x64-gnu/package.json"), "utf8"),
|
|
);
|
|
process.env.LLVM_DIR = join(prefix, "lib/cmake/llvm");
|
|
process.env.SCRIPTC_LLVM_PROFILE_GENERATE = "1";
|
|
delete process.env.SCRIPTC_LLVM_THINLTO;
|
|
buildLinuxHelper({
|
|
arch: "x64",
|
|
libc: "gnu",
|
|
version: manifest.version,
|
|
build: join(work, "helper-build"),
|
|
output: helper,
|
|
});
|
|
|
|
const inputs = await captureHelperInputs(
|
|
helperInputPrograms({ tscTs: resolve(values["tsc-ts"]) }),
|
|
join(work, "inputs"),
|
|
);
|
|
rmSync(raw, { recursive: true, force: true });
|
|
mkdirSync(raw, { recursive: true });
|
|
const out = join(work, "objects");
|
|
for (const input of inputs) {
|
|
rmSync(out, { recursive: true, force: true });
|
|
mkdirSync(out, { recursive: true });
|
|
const result = spawnSync(helper, input.args(out), {
|
|
env: { ...process.env, LLVM_PROFILE_FILE: join(raw, "helper-%p.profraw") },
|
|
encoding: "utf8",
|
|
});
|
|
console.log(`${input.name}: ${input.partitions} partitions, status ${result.status}`);
|
|
if (result.status !== 0) throw new Error(`${input.name}: ${result.stdout}${result.stderr}`);
|
|
}
|
|
|
|
const merged = join(work, "llvm.profdata");
|
|
run("llvm-profdata", ["merge", "-o", merged, ...readdirSync(raw).map((file) => join(raw, file))]);
|
|
const output = resolve(values.output);
|
|
mkdirSync(dirname(output), { recursive: true });
|
|
writeFileSync(output, gzipSync(readFileSync(merged), { level: constants.Z_BEST_COMPRESSION }));
|
|
console.log(`wrote ${output} from ${inputs.length} inputs`);
|