Files
scriptc/tests/harness/llvm-dist.test.ts
T
Chris Tate 4c3a1d9c98 Link the Linux release helpers against LLVM built with ThinLTO and a helper profile
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.
2026-10-25 18:53:25 -05:00

75 lines
2.8 KiB
TypeScript

import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { load } from "js-yaml";
import { afterEach, expect, test } from "vitest";
import {
LLVM_DIST_TAG,
LLVM_VERSION,
helperLlvmDistArgs,
llvmDistAsset,
} from "../../native/llvm-codegen/llvm-dist.mjs";
type Entry = { platform: string; helper?: string; llvm_dist?: string };
const release = load(
readFileSync(new URL("../../.github/workflows/release.yml", import.meta.url), "utf8"),
) as { jobs: Record<string, { strategy?: { matrix?: { include?: Entry[] } } }> };
const entries = release.jobs["build-native-packages"]!.strategy!.matrix!.include!;
const saved = { ...process.env };
afterEach(() => {
process.env = { ...saved };
});
test("every release job that uploads a Linux helper links the same LLVM for it", () => {
// The publish job merges the jobs' artifacts, so two jobs that upload the
// same helper must build it the same way.
const linux = entries.filter((entry) => entry.helper?.startsWith("llvm-linux-"));
expect(linux.map((entry) => entry.platform).sort()).toEqual([
"linux-arm64",
"linux-arm64-musl",
"linux-x64",
"linux-x64-musl",
"wasm32-wasi",
]);
for (const entry of linux)
expect(entry.llvm_dist, entry.platform).toBe(
entry.helper!.includes("-arm64-") ? "arm64" : "x64",
);
for (const entry of entries.filter((entry) => !entry.helper?.startsWith("llvm-linux-")))
expect(entry.llvm_dist, entry.platform).toBeUndefined();
});
test("the distribution names its archives after the LLVM version", () => {
expect(LLVM_DIST_TAG).toMatch(
new RegExp(`^llvm-${LLVM_VERSION.replaceAll(".", "\\.")}-pgo\\.\\d+$`),
);
expect(llvmDistAsset("x64")).toBe(`scriptc-llvm-${LLVM_VERSION}-linux-x64.tar.xz`);
});
test("helper builds use ThinLTO and the profile only when asked", () => {
const dir = mkdtempSync(join(tmpdir(), "scriptc-llvm-dist-"));
try {
delete process.env["SCRIPTC_LLVM_THINLTO"];
delete process.env["SCRIPTC_LLVM_PROFILE_GENERATE"];
expect(helperLlvmDistArgs(dir)).toEqual([
"-DSCRIPTC_LLVM_THINLTO=OFF",
"-DSCRIPTC_LLVM_PROFILE_USE=",
"-DSCRIPTC_LLVM_PROFILE_GENERATE=OFF",
]);
process.env["SCRIPTC_LLVM_THINLTO"] = "1";
const args = helperLlvmDistArgs(dir);
expect(args).toContain("-DCMAKE_CXX_COMPILER=clang++");
expect(args).toContain("-DSCRIPTC_LLVM_THINLTO=ON");
const profile = args
.find((arg) => arg.startsWith("-DSCRIPTC_LLVM_PROFILE_USE="))!
.split("=")[1]!;
// An indexed profile starts with the magic `\xfflprofi`.
expect(readFileSync(profile).subarray(0, 8)).toEqual(
Buffer.from([0xff, 0x6c, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x81]),
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});