Files
scriptc/packages/compiler/src/native/library.ts
Malte Ubl 2372b75ec2 Add an opt-in --optimization=speed posture
Speed is a third native optimization posture next to release (default,
-O2) and dev (-O0). It is optimized exactly like release at the C/LLVM
level and is the switch for optimizations that trade executable size and
build time for run-time speed, so the default release build stays inside
the binary-size budget.

- backend/optimization.ts: the NativeOptimization type, its -O class, and
  cache-key helpers. Release keeps its historical key shapes (absent).
- CLI: build/run/cache warm accept speed; usage text names the trade.
  Bootstrap's routed executable cache mirrors the posture.
- Early executable cache keys and native-feature validation include speed,
  so release, dev, and speed entries never satisfy one another.
- Runtime packs: an optional executable `speed` flavor (-O2, only beside
  release). Speed links it when present and the release objects otherwise;
  library modes link their release flavor. The sanitizer lane follows the
  same flavor key.
- Library profiles accept "speed".
- WASI release-only linker flags apply to speed too.
- bench-runtime: --baseline-optimization/--candidate-optimization and a
  per-contender cold_build_ms.

Release executables are byte-identical to the stack tip for all 14
benchmark workloads (macOS arm64, sha256 of executables built from both
trees).
2026-10-08 15:46:34 -05:00

160 lines
5.5 KiB
TypeScript

import type { NativeOptimization } from "../backend/optimization.js";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path";
import { requireNativeArtifact, runNativeTool } from "../backend/native-tools.js";
import { localizeElfObject, mergeAndLocalizeCoffObjects } from "../backend/object-localize.js";
import { RuntimePackError } from "../backend/runtime-pack-core.js";
import type { NativeRuntimePack } from "../backend/runtime-pack-native.js";
import { executableOptimizationLinkerArgs } from "../backend/targets.js";
import type { NativeToolchain } from "./toolchain.js";
function archiver(toolchain: NativeToolchain): { executable: string; args: string[] } {
if (toolchain.archiver !== undefined)
return { executable: toolchain.archiver, args: toolchain.archiverArgs ?? [] };
const zig = /(?:^|[\\/])zig(?:\.exe)?$/.test(toolchain.linker);
return zig ? { executable: toolchain.linker, args: ["ar"] } : { executable: "ar", args: [] };
}
/** Archive member names are untrusted package data, even after digest verification. */
export function runtimeArchiveMembers(listing: string): string[] {
const names = listing
.trim()
.split(/\r?\n/)
.filter((name) => name !== "" && name !== "__.SYMDEF" && name !== "__.SYMDEF SORTED");
if (
new Set(names).size !== names.length ||
names.some((name) => !/^[A-Za-z0-9_.-]+\.o$/.test(name))
) {
throw new RuntimePackError(
"runtime vendor archive has invalid or duplicate object members",
"invalid",
);
}
return names;
}
/** Localize the runtime without changing the library's declared C ABI. */
export function localizeNativeLibrary(
toolchain: NativeToolchain,
stage: string,
roots: string[],
support: string[],
keep: string[],
): string {
const combined = join(stage, "library.localized.o");
const platform = toolchain.target.platform;
if (platform === "win32") {
writeFileSync(
combined,
mergeAndLocalizeCoffObjects(
roots.map((path) => readFileSync(path)),
support.map((path) => readFileSync(path)),
new Set(keep),
{
roots: roots.map((path) => basename(path)),
support: support.map((path) => basename(path)),
},
),
);
return combined;
}
const ar = archiver(toolchain);
const supportArgs: string[] = [];
if (support.length > 0) {
const archive = join(stage, "support.a");
runNativeTool(ar.executable, [...ar.args, "rcs", archive, ...support], undefined, {
...process.env,
ZERO_AR_DATE: "1",
});
supportArgs.push(archive);
}
if (platform === "darwin") {
if (process.platform !== "darwin")
throw new Error("Mach-O library localization requires a macOS host");
const symbols = join(stage, "public.syms");
writeFileSync(symbols, keep.map((name) => `_${name}\n`).join(""));
runNativeTool(toolchain.relocatableLinker ?? "ld", [
"-r",
...roots,
...supportArgs,
"-o",
combined,
"-exported_symbols_list",
symbols,
]);
} else if (platform === "linux") {
runNativeTool(toolchain.linker, [
...toolchain.linkerArgs,
"-target",
toolchain.target.linkerTargetTriple,
"-nostdlib",
"-r",
...roots,
...supportArgs,
"-o",
combined,
]);
writeFileSync(combined, localizeElfObject(readFileSync(combined), new Set(keep)));
} else throw new Error(`library localization is unavailable for ${toolchain.target.name}`);
requireNativeArtifact(combined);
return combined;
}
export function archiveNativeLibrary(options: {
toolchain: NativeToolchain;
programObject: string;
outputPath: string;
stage: string;
runtime: NativeRuntimePack;
localizeSymbols?: string[];
}): void {
const { toolchain, stage } = options;
const ar = archiver(toolchain);
const program = options.programObject;
const support = [...options.runtime.runtimeObjects];
for (let index = 0; index < options.runtime.archives.length; index++) {
const archive = options.runtime.archives[index]!;
const members = runtimeArchiveMembers(runNativeTool(ar.executable, [...ar.args, "t", archive]));
const directory = join(stage, `vendor-${index}`);
mkdirSync(directory);
if (members.length > 0)
runNativeTool(ar.executable, [...ar.args, "x", archive, ...members], directory);
support.push(...members.map((name) => join(directory, name)));
}
const objects =
options.localizeSymbols === undefined
? [program, ...support]
: [localizeNativeLibrary(toolchain, stage, [program], support, options.localizeSymbols)];
runNativeTool(ar.executable, [...ar.args, "rcs", options.outputPath, ...objects], undefined, {
...process.env,
ZERO_AR_DATE: "1",
});
requireNativeArtifact(options.outputPath);
}
export function linkNativeWasmLibrary(options: {
toolchain: NativeToolchain;
programObject: string;
outputPath: string;
runtime: NativeRuntimePack;
optimization: NativeOptimization;
exports: string[];
}): void {
const { toolchain, runtime } = options;
runNativeTool(toolchain.linker, [
...toolchain.linkerArgs,
"-target",
toolchain.target.linkerTargetTriple,
"-mexec-model=reactor",
...executableOptimizationLinkerArgs("wasi", options.optimization),
...options.exports.map((name) => `-Wl,--export=${name}`),
options.programObject,
...runtime.runtimeObjects,
...runtime.archives,
...runtime.systemLibraries.map((name) => `-l${name}`),
"-o",
options.outputPath,
]);
requireNativeArtifact(options.outputPath);
}