mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
Re-lands perf/h02-runtime-lto (6d373317, b62448f3) behind the speed posture. - Runtime packs gain an executable `speed` flavor whenever the host LLVM helper can run. Its static units are emitted through the helper's `runtime-unit` command (promoted unit-local symbols, object and import bitcode from one module); its SCR_DYNAMIC variants reference the release objects. Release, dev, and library flavors and the vendor archives are built exactly as before (the rebuilt macOS pack's flavors and archives are identical to the baseline's). - Speed program builds load the speed flavor's bitcode and pass it to the helper (`--import-bitcode`); release builds never do. loadRuntimeBitcode only reads a real speed flavor, never release. - The helper drops the emitter's inert sanitize_address attributes only when it imports, so release modules reach the pipeline unchanged. - Tests: runtime-pack.test.ts covers speed-variant bitcode selection, verification, and that release-flavor bitcode is never imported; native-codegen.test.ts covers helper arguments and cache keys. Release executables stay byte-identical to the stack tip with the rebuilt helper (14/14 workloads, macOS).
101 lines
3.6 KiB
JavaScript
101 lines
3.6 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);
|
|
|
|
/* Release executable runtime units ship two artifacts produced from one
|
|
* optimized LLVM module: the native object and the bitcode the LLVM helper
|
|
* imports small runtime functions from (available_externally, so program
|
|
* code can inline them while the object stays the only linked definition).
|
|
*
|
|
* The runtime compiler optimizes the C source to bitcode; the host LLVM
|
|
* helper's `runtime-unit` command then promotes translation-unit-local
|
|
* symbols to hidden unit-qualified globals and emits both artifacts. 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) units are compiled to objects directly and
|
|
* ship no bitcode; program builds then simply import nothing. */
|
|
|
|
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}` };
|
|
} catch {
|
|
if (explicit !== undefined) throw new Error(`SCRIPTC_LLVM_CODEGEN is not usable: ${helper}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Compile one unit through the helper. `compileBitcode(output)` must run
|
|
* the runtime compiler with the unit's exact flags plus -emit-llvm.
|
|
* Returns false, leaving no artifacts behind, when the helper cannot emit
|
|
* this unit; the caller then compiles the object directly without bitcode,
|
|
* so a pack never fails to build because of the import path. */
|
|
export async function buildRuntimeUnit({
|
|
helper,
|
|
compileBitcode,
|
|
object,
|
|
bitcode,
|
|
triple,
|
|
tag,
|
|
sections,
|
|
}) {
|
|
const optimized = object.replace(/\.o$/, ".opt.bc");
|
|
// A runtime compiler failure is a real build error and surfaces as one.
|
|
await compileBitcode(optimized);
|
|
try {
|
|
await run(helper.path, [
|
|
"runtime-unit",
|
|
"--input",
|
|
optimized,
|
|
"--object",
|
|
object,
|
|
"--bitcode",
|
|
bitcode,
|
|
"--target",
|
|
triple,
|
|
"--tag",
|
|
tag,
|
|
...(sections ? ["--function-sections", "--data-sections"] : []),
|
|
]);
|
|
return true;
|
|
} catch (error) {
|
|
const detail = (error.stderr || error.message || String(error)).trim().split("\n")[0];
|
|
process.stderr.write(`runtime-unit: ${tag}: emitting without import bitcode (${detail})\n`);
|
|
await Promise.all([rm(object, { force: true }), rm(bitcode, { force: true })]);
|
|
return false;
|
|
} finally {
|
|
await rm(optimized, { force: true });
|
|
}
|
|
}
|