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.
115 lines
4.3 KiB
JavaScript
115 lines
4.3 KiB
JavaScript
/* Inputs of the LLVM helper, captured once and replayed with any helper:
|
|
* scripts/refresh-llvm-profile.mjs trains the helper's profile on them and
|
|
* scripts/check-llvm-dist.mjs compares helpers on them. Each input is a
|
|
* program's LLVM text from `scriptc build --emit llvm` and the `emit`
|
|
* arguments the compiler passes for an executable build of it
|
|
* (native-codegen.ts), partitions included. The partition cache is left out,
|
|
* so every replay compiles every partition. */
|
|
import { spawnSync } from "node:child_process";
|
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
const EFFECT_FIXTURES = [
|
|
"async-sleep",
|
|
"async-promise-typed",
|
|
"async-timeout",
|
|
"async-resume-timer",
|
|
"async-yield",
|
|
];
|
|
|
|
/** The programs: tsc-ts (when a checkout is given), five Effect fixtures,
|
|
* the runtime benchmark workloads, every 50th numbered corpus program, and
|
|
* `extra` directories of standalone programs. */
|
|
export function helperInputPrograms({ tscTs, extra = [] } = {}) {
|
|
const programs = [];
|
|
if (tscTs !== undefined)
|
|
programs.push({ name: "tsc-ts", cwd: join(tscTs, "packages/tsc-ts"), entry: "src/main.ts" });
|
|
for (const fixture of EFFECT_FIXTURES)
|
|
programs.push({
|
|
name: `effect-${fixture}`,
|
|
cwd: root,
|
|
entry: `tests/fixtures/effect/${fixture}.ts`,
|
|
args: ["--npm-static", "effect,fast-check,pure-rand"],
|
|
});
|
|
const suite = join(root, "benchmarks/runtime");
|
|
for (const file of readdirSync(join(suite, "workloads"))
|
|
.filter((f) => f.endsWith(".json"))
|
|
.sort()) {
|
|
const { entry } = JSON.parse(readFileSync(join(suite, "workloads", file), "utf8"));
|
|
// Variants of a workload share its entry and differ only in arguments.
|
|
if (!programs.some((program) => program.cwd === suite && program.entry === entry))
|
|
programs.push({ name: `wl-${file.slice(0, -5)}`, cwd: suite, entry });
|
|
}
|
|
const corpus = join(root, "tests/corpus");
|
|
for (const file of readdirSync(corpus).sort()) {
|
|
const number = /^(\d+)-.*\.ts$/.exec(file)?.[1];
|
|
if (number !== undefined && Number(number) % 50 === 0)
|
|
programs.push({ name: `corpus-${file.slice(0, -3)}`, cwd: corpus, entry: file });
|
|
}
|
|
for (const dir of extra)
|
|
for (const file of readdirSync(dir)
|
|
.filter((f) => f.endsWith(".ts"))
|
|
.sort())
|
|
programs.push({ name: `extra-${file.slice(0, -3)}`, cwd: dir, entry: file });
|
|
return programs;
|
|
}
|
|
|
|
/** Writes `<dir>/<name>.ll` for each program with this checkout's compiler
|
|
* and returns the inputs that compiled: `{ name, input, outputs, args(out) }`,
|
|
* where `args(out)` gives the helper's arguments writing into `out`. */
|
|
export async function captureHelperInputs(programs, dir) {
|
|
const { nativeProgramPartitions } = await import(
|
|
join(root, "packages/compiler/dist/backend/native-codegen-core.js")
|
|
);
|
|
const cli = join(root, "packages/cli/dist/main.js");
|
|
const target =
|
|
process.arch === "arm64" ? "aarch64-unknown-linux-gnu" : "x86_64-unknown-linux-gnu";
|
|
mkdirSync(dir, { recursive: true });
|
|
const inputs = [];
|
|
for (const program of programs) {
|
|
const input = join(dir, `${program.name}.ll`);
|
|
if (!existsSync(input)) {
|
|
const built = spawnSync(
|
|
process.execPath,
|
|
[cli, "build", program.entry, "--emit", "llvm", "-o", input, ...(program.args ?? [])],
|
|
{ cwd: program.cwd, encoding: "utf8" },
|
|
);
|
|
if (built.status !== 0) {
|
|
console.log(`skipped ${program.name}: the compiler refused it`);
|
|
continue;
|
|
}
|
|
}
|
|
const partitions = nativeProgramPartitions(statSync(input).size);
|
|
const source = join(program.cwd, program.entry);
|
|
inputs.push({
|
|
name: program.name,
|
|
input,
|
|
partitions,
|
|
args: (out) => [
|
|
"emit",
|
|
"--input",
|
|
input,
|
|
...Array.from({ length: partitions }, (_, i) => [
|
|
"--output",
|
|
join(out, `${program.name}.part${i}.o`),
|
|
]).flat(),
|
|
"--filetype",
|
|
"obj",
|
|
"--target",
|
|
target,
|
|
"--opt-level",
|
|
"2",
|
|
"--relocation-model",
|
|
"pic",
|
|
"--diagnostic-format",
|
|
"json",
|
|
"--source-path",
|
|
source,
|
|
],
|
|
});
|
|
}
|
|
return inputs;
|
|
}
|