Files
scriptc/packages/runtime-pack-common/scripts/runtime-unit.mjs
T
Chris Tate c4105d23a7 Stop building and shipping runtime import bitcode
Program builds have not imported runtime bitcode since fc38e6279, but
every runtime pack still built and listed it: release units through the
helper's prune-import, speed units as runtime-unit's second output. That
is 24 MB of the linux-x64-gnu pack.

Building that bitcode also cost scr_glibc_math its runtime profile: the
pruned import module keeps the floor, ceil and trunc ifuncs without their
resolvers and fails verification, and runtime-unit built it before the
object, so the whole speed unit fell back to a direct compile. The build
printed only the first line of the helper's stderr, a CPU feature
warning.

- runtime-unit's --bitcode is optional (feature
  "runtime-unit-optional-bitcode"), and pack builds no longer pass it or
  run prune-import. Speed objects come out as before, scr_glibc_math's now
  with the runtime profile.
- Pack manifests list no bitcode, and the compiler no longer validates the
  field. verify and share-artifacts check objects only.
- A unit the helper can't emit reports the helper's JSON diagnostic.
- The zero-cost gate names changed runtime functions from the objects'
  disassembly instead of the bitcode.
2026-10-29 17:58:37 -05:00

143 lines
5.3 KiB
JavaScript

import { execFile } from "node:child_process";
import { access, constants, rm } from "node:fs/promises";
import { join } from "node:path";
import { promisify } from "node:util";
const run = promisify(execFile);
/* Speed units are emitted through the host LLVM helper: the runtime
* compiler compiles the C source to bitcode, and the helper's `runtime-unit`
* command promotes translation-unit-local symbols to hidden unit-qualified
* globals and emits the object. With the target's committed runtime profile,
* the runtime compiler emits the bitcode unoptimized and the helper also
* optimizes it with the profile (build.mjs), so one LLVM reads and writes
* every profile. Runtime packs ship objects only: program builds do not
* import runtime bitcode.
*
* Every host helper supports every scriptc target, so packs keep building
* on their usual hosts. Without a runnable host helper (or with
* SCRIPTC_RUNTIME_BITCODE=0) a pack has no speed flavor. */
function hostHelperPackage() {
if (process.platform === "darwin") return `llvm-darwin-${process.arch}`;
if (process.platform === "win32") return `llvm-win32-${process.arch}-msvc`;
if (process.platform === "linux") {
const musl = process.report?.getReport?.().header?.glibcVersionRuntime === undefined;
return `llvm-linux-${process.arch}-${musl ? "musl" : "gnu"}`;
}
return null;
}
export async function resolveRuntimeUnitHelper(repoRoot, triple) {
if (process.env.SCRIPTC_RUNTIME_BITCODE === "0") return null;
const explicit = process.env.SCRIPTC_LLVM_CODEGEN;
const pkg = hostHelperPackage();
const helper =
explicit ??
(pkg === null
? null
: join(
repoRoot,
"packages",
pkg,
"bin",
process.platform === "win32" ? "scriptc-llvm-codegen.exe" : "scriptc-llvm-codegen",
));
if (helper === null) return null;
try {
await access(helper, constants.X_OK);
const { stdout } = await run(helper, ["version", "--format=json"]);
const version = JSON.parse(stdout);
if (!version.supported_targets?.includes(triple)) return null;
return {
path: helper,
identity: `scriptc-llvm-codegen ${version.llvm_version}`,
// Commands and options newer than the protocol version, such as
// "runtime-unit-profile": `runtime-unit` takes --opt-level,
// --profile-use and --profile-generate (older helpers only promote and
// emit); "runtime-unit-optional-bitcode": it emits the object without
// the import bitcode.
features: Array.isArray(version.features) ? version.features : [],
};
} catch {
if (explicit !== undefined) throw new Error(`SCRIPTC_LLVM_CODEGEN is not usable: ${helper}`);
return null;
}
}
/** The helper's diagnostic from a failed command: its JSON line, which
* follows any LLVM warnings on the same streams. */
function helperFailure(error) {
const lines = [error.stdout, error.stderr]
.filter((text) => typeof text === "string")
.join("\n")
.split("\n")
.map((line) => line.trim())
.filter((line) => line !== "");
return (
lines.find((line) => line.startsWith("{")) ??
lines.find((line) => !line.includes("is not a recognized feature")) ??
lines[0] ??
error.message ??
String(error)
);
}
/** Compile one unit through the helper. `compileBitcode(output)` must run
* the runtime compiler with the unit's exact flags plus -emit-llvm.
* `helperArgs` asks the helper to optimize the unit itself (`--opt-level`,
* with `--profile-use` or `--profile-generate`), for bitcode the runtime
* compiler emitted unoptimized.
* Returns false, leaving no artifacts behind, when the helper cannot emit
* this unit; the caller then compiles the object directly. Otherwise
* returns an object, with the helper's `profile` match counts when a profile
* guided the unit, or the profile names of the unit's `functions` when it
* was instrumented. */
export async function buildRuntimeUnit({
helper,
compileBitcode,
helperArgs = [],
object,
triple,
tag,
sections,
}) {
const optimized = object.replace(/\.o$/, ".opt.bc");
// Helpers older than "runtime-unit-optional-bitcode" always write it.
const bitcode = helper.features.includes("runtime-unit-optional-bitcode")
? null
: object.replace(/\.o$/, ".import.bc");
// A runtime compiler failure is a real build error and surfaces as one.
await compileBitcode(optimized);
try {
const { stdout } = await run(helper.path, [
"runtime-unit",
"--input",
optimized,
"--object",
object,
...(bitcode === null ? [] : ["--bitcode", bitcode]),
"--target",
triple,
"--tag",
tag,
...(sections ? ["--function-sections", "--data-sections"] : []),
...helperArgs,
]);
const report = stdout.trim() === "" ? {} : JSON.parse(stdout.trim().split("\n").pop());
return {
...(report.profile === undefined ? {} : { profile: report.profile }),
...(report.functions === undefined ? {} : { functions: report.functions }),
};
} catch (error) {
process.stderr.write(
`runtime-unit: ${tag}: compiling the object directly (${helperFailure(error)})\n`,
);
await rm(object, { force: true });
return false;
} finally {
await rm(optimized, { force: true });
if (bitcode !== null) await rm(bitcode, { force: true });
}
}