From 23d95a99fd923516d4ebb30fd77d33a9aa3dccbc Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 28 Oct 2026 05:44:38 -0500 Subject: [PATCH] Name program modules by the entry's file name so profiles match in every checkout Each partition's source file name was the program entry's absolute path, and profiles name a module's local functions after it. Since programs keep partition-local functions internal (#2449), a profile collected in one directory missed those functions in builds from any other: a native compiler profile collected on main named 27% of its records, holding 2.2% of its counts, after the checkout's path. Modules now carry the entry's file name. The partition cache key still includes the full path. --- docs/content/docs/cli.mdx | 2 +- native/llvm-codegen/src/emit.cpp | 13 +++++++++++-- packages/compiler/test/native-profile.test.ts | 10 ++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index f2d91f1bd..3c6340bdb 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -266,7 +266,7 @@ $ scriptc merge-profiles -o app.profdata profiles/*.profraw $ scriptc build app.ts --optimization=speed --profile-use app.profdata ``` -Train on workloads like the ones the program will run: code the training never reached is optimized as without a profile. The profile can be committed and reused as the program changes. Functions whose control flow changed since the profile was collected lose their profile, and the rest keep theirs, so regenerate it when much of the program has changed. [Build parallelism](/docs/how-it-works#build-parallelism) describes what a profile adds to build time. +Train on workloads like the ones the program will run: code the training never reached is optimized as without a profile. The profile can be committed and reused as the program changes, and in other checkouts and on other machines: it names functions by the entry's file name, not its directory. Functions whose control flow changed since the profile was collected lose their profile, and the rest keep theirs, so regenerate it when much of the program has changed. [Build parallelism](/docs/how-it-works#build-parallelism) describes what a profile adds to build time. `scriptc build --profile-use` prints one line on stderr with how the profile matched the program's functions: diff --git a/native/llvm-codegen/src/emit.cpp b/native/llvm-codegen/src/emit.cpp index b7e0014a5..3df2e2561 100644 --- a/native/llvm-codegen/src/emit.cpp +++ b/native/llvm-codegen/src/emit.cpp @@ -42,6 +42,7 @@ #include "llvm/Support/Caching.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" #include "llvm/Support/SHA256.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/ThreadPool.h" @@ -381,6 +382,14 @@ std::optional inspectProfile(const EmitOptions &Options) { return std::nullopt; } +// The source file name a program's modules carry: the entry's file name, +// without its directory. Profiles name a module's local functions after it +// (";"), so a profile collected in one checkout matches the +// builds of every other one. +std::string moduleSourceName(StringRef SourcePath) { + return sys::path::filename(SourcePath).str(); +} + // Owns the analysis managers a pipeline needs for one module. struct Pipeline { PassInstrumentationCallbacks Callbacks; @@ -703,7 +712,7 @@ Expected> loadPartition(const EmitOptions &Options, return createStringError(inconvertibleErrorCode(), Stream.str()); } if (!Options.SourcePath.empty()) - Mod->setSourceFileName(Options.SourcePath); + Mod->setSourceFileName(moduleSourceName(Options.SourcePath)); Mod->setTargetTriple(Triple(Options.Target)); Mod->setDataLayout(Machine.createDataLayout()); if (std::optional Failure = verify(*Mod, "verification_failed")) @@ -1555,7 +1564,7 @@ int emit(const EmitOptions &Options) { return reportError("invalid_ir", Stream.str(), Options.DiagnosticFormat); } if (!Options.SourcePath.empty()) - Mod->setSourceFileName(Options.SourcePath); + Mod->setSourceFileName(moduleSourceName(Options.SourcePath)); std::string LookupError; std::unique_ptr Machine = diff --git a/packages/compiler/test/native-profile.test.ts b/packages/compiler/test/native-profile.test.ts index a6bdfe241..ffeab1b77 100644 --- a/packages/compiler/test/native-profile.test.ts +++ b/packages/compiler/test/native-profile.test.ts @@ -352,6 +352,16 @@ describe.runIf(helper !== null)("LLVM profile-guided optimization", () => { }); describe.runIf(profileLinker !== null)("with the profile runtime", () => { + test("profiles name local functions after the entry's file name, not its directory", async () => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-profile-names-")); + dirs.push(dir); + const { profile } = await collect(dir, program(), 1, "names"); + const { stdout } = await execFileAsync(helper!, ["profile-functions", "--input", profile]); + const { functions } = JSON.parse(stdout) as { functions: string[] }; + expect(functions.filter((name) => name.startsWith("program.ts;")).length).toBeGreaterThan(0); + expect(functions.filter((name) => name.includes("/"))).toEqual([]); + }); + for (const outputs of [1, 4]) { test(`${outputs === 1 ? "one module" : "partitions"}: the merged profile guides emission`, async () => { const dir = await mkdtemp(join(tmpdir(), "scriptc-profile-use-"));