Compile dev executables of more than 64 KiB in partitions (#2440)

This commit is contained in:
Chris Tate
2026-10-27 18:16:13 -05:00
committed by GitHub
5 changed files with 33 additions and 9 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ Parsing, type checking, lowering, and LLVM IR generation run one after another o
### Partitions
The LLVM helper divides an executable's LLVM IR into partitions by its size. Up to 1 MiB of LLVM text is one partition, and up to 2 MiB is four. Larger programs get one partition per 4 MiB of LLVM text, rounded up to a power of two, and at least 8 and at most 64. `--emit=llvm` writes this text, so its file size shows how many partitions a program gets.
The LLVM helper divides an executable's LLVM IR into partitions by its size. Up to 1 MiB of LLVM text is one partition, and up to 2 MiB is four. Larger programs get one partition per 4 MiB of LLVM text, rounded up to a power of two, and at least 8 and at most 64. `--emit=llvm` writes this text, so its file size shows how many partitions a program gets. `dev` executables, whose partitions are compiled on their own with nothing imported, are divided from 64 KiB: up to 128 KiB is four partitions, and larger `dev` programs get at least 8.
<table>
<thead>
@@ -14,20 +14,32 @@ const PROGRAM_PARTITION_BYTES = 4 * 1024 * 1024;
const MIN_PARTITION_BYTES = 512 * 1024;
const MIN_PROGRAM_PARTITIONS = 8;
const MAX_PROGRAM_PARTITIONS = 64;
/** SINGLE_PARTITION_BYTES and MIN_PARTITION_BYTES of unoptimized (dev)
* executables. */
const UNOPTIMIZED_SINGLE_PARTITION_BYTES = 64 * 1024;
const UNOPTIMIZED_MIN_PARTITION_BYTES = 32 * 1024;
/** Executable objects larger than SINGLE_PARTITION_BYTES are divided into
* partitions that the helper simplifies, optimizes and compiles concurrently,
* importing small functions across partitions for inlining, and reuses
* individually across builds. A divided module gets at least
* MIN_PROGRAM_PARTITIONS so that mid-size programs use several cores. The
* count is a power of two that depends only on the module's size, on every
* target, so artifacts never vary with the host and ordinary edits keep every
* function in its partition. */
export function nativeProgramPartitions(llvmBytes: number): number {
if (llvmBytes <= SINGLE_PARTITION_BYTES) return 1;
* count is a power of two that depends only on the module's size and
* whether it is optimized, on every target, so artifacts never vary with the
* host and ordinary edits keep every function in its partition.
*
* Unoptimized (dev) partitions are compiled on their own with nothing
* imported, so they cost no run time and an edit compiles only the partitions
* whose functions it changes: dev executables are divided from
* UNOPTIMIZED_SINGLE_PARTITION_BYTES, at least
* UNOPTIMIZED_MIN_PARTITION_BYTES each. */
export function nativeProgramPartitions(llvmBytes: number, unoptimized = false): number {
const single = unoptimized ? UNOPTIMIZED_SINGLE_PARTITION_BYTES : SINGLE_PARTITION_BYTES;
const least = unoptimized ? UNOPTIMIZED_MIN_PARTITION_BYTES : MIN_PARTITION_BYTES;
if (llvmBytes <= single) return 1;
const required = Math.max(
Math.ceil(llvmBytes / PROGRAM_PARTITION_BYTES),
Math.min(MIN_PROGRAM_PARTITIONS, Math.ceil(llvmBytes / MIN_PARTITION_BYTES)),
Math.min(MIN_PROGRAM_PARTITIONS, Math.ceil(llvmBytes / least)),
);
return Math.min(MAX_PROGRAM_PARTITIONS, 2 ** Math.ceil(Math.log2(required)));
}
@@ -408,6 +408,14 @@ test("program partitions depend only on module size", () => {
expect(nativeProgramPartitions(32 * megabyte + 1)).toBe(16);
expect(nativeProgramPartitions(100 * megabyte)).toBe(32);
expect(nativeProgramPartitions(1024 * megabyte)).toBe(64);
// Unoptimized (dev) programs are divided from 64 KiB.
const kilobyte = 1024;
expect(nativeProgramPartitions(64 * kilobyte, true)).toBe(1);
expect(nativeProgramPartitions(64 * kilobyte + 1, true)).toBe(4);
expect(nativeProgramPartitions(128 * kilobyte, true)).toBe(4);
expect(nativeProgramPartitions(128 * kilobyte + 1, true)).toBe(8);
expect(nativeProgramPartitions(megabyte, true)).toBe(8);
expect(nativeProgramPartitions(32 * megabyte + 1, true)).toBe(16);
expect(nativePartitionPaths("/build.d/program.o", 3)).toEqual([
"/build.d/program.o",
"/build.d/program.part1.o",
+3 -1
View File
@@ -784,7 +784,9 @@ async function emitNativeProgramObject(
// An optimized program that stays one partition is optimized whole and
// generates code for its parts concurrently.
const codegenParts = single || opts.optimization === "dev" ? 1 : nativeCodegenParts(llvmBytes);
const partitions = single ? 1 : Math.max(nativeProgramPartitions(llvmBytes), codegenParts);
const partitions = single
? 1
: Math.max(nativeProgramPartitions(llvmBytes, opts.optimization === "dev"), codegenParts);
try {
const artifact = await emitNativeArtifact({
outputPath: linkPath,
+3 -1
View File
@@ -386,7 +386,9 @@ export class NativeCompiler {
// An optimized program that stays one partition is optimized whole and
// generates code for its parts concurrently.
const codegenParts = library || optimization === "dev" ? 1 : nativeCodegenParts(size);
const partitions = library ? 1 : Math.max(nativeProgramPartitions(size), codegenParts);
const partitions = library
? 1
: Math.max(nativeProgramPartitions(size, optimization === "dev"), codegenParts);
return this.emitObject(
input,
output,