mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Compile dev executables of more than 64 KiB in partitions
A dev executable was one partition up to 1 MiB of LLVM text, so every edit compiled the whole program again. Unoptimized partitions are compiled on their own, with nothing imported across them, so dividing a program does not change its code's quality, and the helper reuses each partition an edit does not change from the partition cache. nativeProgramPartitions now takes whether the program is optimized. Dev executables are divided from 64 KiB of LLVM text, with at least 32 KiB per partition: four partitions up to 128 KiB, then at least 8 as before. Release and speed executables keep their partition and code generation counts.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user