Tune LLVM splitting for dev libraries (#193)

- Lower the dev-library split floor and bucket target for faster localized rebuilds.
- Preserve executable, release, sanitized, and cache-validation behavior.
This commit is contained in:
Chris Tate
2026-08-21 21:58:03 -05:00
committed by GitHub
parent af333016d4
commit 30d45bd126
3 changed files with 33 additions and 3 deletions
@@ -3,7 +3,7 @@ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterAll, expect, test } from "vitest";
import { splitLlvmProgram } from "./split.js";
import { splitLlvmLibraryProgram, splitLlvmProgram } from "./split.js";
const scratch: string[] = [];
afterAll(async () => {
@@ -77,6 +77,20 @@ test("thread-local program state conservatively keeps the single-TU path", () =>
expect(splitLlvmProgram(tls, { minimumBytes: 0, targetBytes: 64 * 1024 })).toBeNull();
});
test("dev libraries split at the measured 2MB crossover while executables retain 4MB", () => {
const body = Array.from({ length: 700 }, (_, i) =>
`define internal i64 @library_pad_${i}() #0 {\nentry:\n ; ${"x".repeat(3072)}\n ret i64 ${i}\n}\n`,
).join("\n");
const source = SAMPLE.replace("define i64 @public_entry", `${body}\ndefine i64 @public_entry`);
expect(Buffer.byteLength(source)).toBeGreaterThan(2 * 1024 * 1024);
expect(Buffer.byteLength(source)).toBeLessThan(4 * 1024 * 1024);
expect(splitLlvmProgram(source)).toBeNull();
expect(splitLlvmLibraryProgram(source)?.shards).toHaveLength(5);
const belowCrossover = source.slice(0, Math.floor(1.9 * 1024 * 1024));
expect(splitLlvmLibraryProgram(belowCrossover)).toBeNull();
});
test("every shard compiles and its merged object exposes only canonical public definitions", async () => {
const body = Array.from({ length: 120 }, (_, i) =>
`define internal i64 @pad_${i}() #0 {\nentry:\n ; ${"x".repeat(2048)}\n ret i64 ${i}\n}\n`,
@@ -44,6 +44,14 @@ export interface LlvmProgramSplitOptions {
const DEFAULT_TARGET_BYTES = 2 * 1024 * 1024;
const DEFAULT_MINIMUM_BYTES = 4 * 1024 * 1024;
// Library edits pay a relocatable merge/archive cost after shard compilation,
// but their stable per-bucket object cache starts winning earlier than the
// executable lane's whole-program posture. Node 24 Linux profiling across
// 1.62–3.24MB generated modules found the crossover at roughly 2MB; four
// function buckets were the best general tradeoff for the 2.15MB target and
// remained profitable through 3.24MB. Executables retain the defaults above.
const LIBRARY_TARGET_BYTES = 768 * 1024;
const LIBRARY_MINIMUM_BYTES = 2 * 1024 * 1024;
interface FunctionDef {
source: string;
@@ -236,3 +244,11 @@ export function splitLlvmProgram(
],
};
}
/** Dev-library policy tuned independently from executable splitting. */
export function splitLlvmLibraryProgram(source: string): LlvmProgramSplit | null {
return splitLlvmProgram(source, {
minimumBytes: LIBRARY_MINIMUM_BYTES,
targetBytes: LIBRARY_TARGET_BYTES,
});
}
+2 -2
View File
@@ -3,7 +3,7 @@ import { basename, dirname, join, resolve } from "node:path";
import { buildCacheRoot, CcCompileError, compileC, compileLibArchive, executableNativeEnvironmentFingerprint, mobileLibraryTarget, mobileTargetRefusal, prepareBuildCacheRoot, pruneBuildCache, resolveCc, targetPlatform } from "./backend/cc.js";
import { emitModule } from "./backend/emission/emitter.js";
import { emitLlvmModule, LlvmUnsupportedError } from "./backend/llvm/emitter.js";
import { splitLlvmProgram } from "./backend/llvm/split.js";
import { splitLlvmLibraryProgram, splitLlvmProgram } from "./backend/llvm/split.js";
import { rebaseLibrarySourceComments, replaceLibraryIdentity, stripLibraryIdentity, stripLibrarySourceComments } from "./backend/library-identity.js";
import { checkerPanicDiag, ffiNativeBuildDiag, libAsyncExportDiag, libAsyncSurfaceDiag, libExportUnresolvedDiag, libGenericExportDiag, libIntBoundaryDiag, libNpmIneligibleDiag, libSidecarDiag, libUnmappableSignatureDiag, iceDiag, isCheckerPanic, LIB_INBOUND_BYTES_TRAP_CODE, LIB_RUNTIME_TRAP_CODES, type ScrDiagnostic } from "./diagnostics/diagnostic.js";
import { checkLibraryIntegerSlots, classSeed, hasIntSlots, numberCarrierKind, type FnIntSlots, type IntSlotConfig } from "./library/int-infer.js";
@@ -1748,7 +1748,7 @@ async function compileLibraryNative(
}
const llvmSplit =
profile.emission === "llvm" && profile.optimization === "dev" && !sanitize && programSource !== undefined
? splitLlvmProgram(programSource)
? splitLlvmLibraryProgram(programSource)
: null;
await compileLibArchive({
cPath,