[llvm] fe5f49f - [DTLTO] Do not serialize inputs that hit in the ThinLTO cache (#204104)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 26 08:03:42 PDT 2026
Author: Ben Dunbobbin
Date: 2026-06-26T16:03:36+01:00
New Revision: fe5f49f84f1e41a3266d215be01f3fe830e75d5d
URL: https://github.com/llvm/llvm-project/commit/fe5f49f84f1e41a3266d215be01f3fe830e75d5d
DIFF: https://github.com/llvm/llvm-project/commit/fe5f49f84f1e41a3266d215be01f3fe830e75d5d.diff
LOG: [DTLTO] Do not serialize inputs that hit in the ThinLTO cache (#204104)
To handle bitcode inputs that are not in individual files on disk, such
as members of non-thin archives, DTLTO serializes those inputs to
temporary individual bitcode files.
This patch changes LLVM to serialize only uncached input modules and any
modules they import from.
For a link of Clang 22 (debug build with sanitizers and
instrumentation), I performed measurements with and without this patch
for an optimized toolchain (PGO non-LTO, based on recent main commit
c264e07c2f3d9f25a2526e69926daea3a68be74b). The measurements were run on:
- Windows 11 Pro build 26200, AMD Family 25 at approximately 4.5 GHz,
16 cores / 32 threads, and 64 GB RAM.
- Ubuntu 24.04.3 LTS, Ryzen 9 5950X with 32 threads, and 62 GiB RAM.
There was no difference in serialization time when the cache was
disabled.
When the cache was enabled and all compilations hit in the cache,
serialization was eliminated, as was the time spent deleting the
previously serialized temporary files, which are no longer created. Mean
wall-clock time improved by about 10% on both machines in this scenario.
Added:
cross-project-tests/dtlto/cache-serialization.test
Modified:
llvm/include/llvm/DTLTO/DTLTO.h
llvm/lib/DTLTO/DTLTO.cpp
llvm/lib/DTLTO/DTLTOInputFiles.cpp
Removed:
################################################################################
diff --git a/cross-project-tests/dtlto/cache-serialization.test b/cross-project-tests/dtlto/cache-serialization.test
new file mode 100644
index 0000000000000..9be19a4b9495c
--- /dev/null
+++ b/cross-project-tests/dtlto/cache-serialization.test
@@ -0,0 +1,91 @@
+REQUIRES: ld.lld
+
+# Show that DTLTO input file serialization interacts correctly with the
+# ThinLTO cache.
+#
+# To handle bitcode inputs that are not in individual files on disk, such as
+# members of non-thin archives, DTLTO serializes such input bitcode to a
+# temporary individual file on disk. This serialization should only be
+# performed if the backend compilation is required. A backend compilation is
+# only required if there is a miss in the ThinLTO cache for that module, or a
+# module is imported by a module that has missed in the ThinLTO cache.
+#
+# Test DTLTO input serialization across three cases:
+# 1. None of the backend compilations hit in the ThinLTO cache.
+# 2. All of the backend compilations hit in the ThinLTO cache.
+# 3. Some backend compilations hit in the ThinLTO cache and some miss.
+
+RUN: rm -rf %t && split-file %s %t && cd %t
+
+RUN: %clang --target=x86_64-linux-gnu -O2 -flto=thin -c f1.c f2.c
+RUN: llvm-ar rcs f1.a f1.o
+RUN: llvm-ar rcs f2.a f2.o
+
+RUN: mkdir cache.dir
+
+DEFINE: %{link} = \
+DEFINE: %clang --target=x86_64-linux-gnu -fuse-ld=lld -shared -nostdlib \
+DEFINE: -O2 -flto=thin \
+DEFINE: -fthinlto-distributor=%python \
+DEFINE: -Xthinlto-distributor=%llvm_src_root/utils/dtlto/local.py \
+DEFINE: -Wl,--thinlto-cache-dir=cache.dir \
+DEFINE: -Wl,--save-temps
+
+# 1. None of the backend compilations hit in the ThinLTO cache.
+#
+# Both f1.o and f2.o require backend compilation, so both should be serialized.
+# Each backend compilation should produce:
+# * a native object
+# * a thinlto.bc file
+# * a serialized temporary bitcode file (an extracted archive member)
+RUN: mkdir none
+RUN: %{link} -Wl,--whole-archive f1.a f2.a -o none/out.elf
+RUN: ls none | sort | FileCheck %s --check-prefix=NONE
+NONE-NOT: {{^f[1-3]}}
+NONE: {{^}}f1.a(f1.o at [[#F1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID:]].native.o{{$}}
+NONE-NEXT: {{^}}f1.a(f1.o at [[#F1_OFFSET]]).1.[[#%X,HEXPID]].1.[[#PID]].native.o.thinlto.bc{{$}}
+NONE-NEXT: {{^}}f1.a(f1.o at [[#F1_OFFSET]]).1.[[#%X,HEXPID]].o{{$}}
+NONE-NEXT: {{^}}f2.a(f2.o at [[#F2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o{{$}}
+NONE-NEXT: {{^}}f2.a(f2.o at [[#F2_OFFSET]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o.thinlto.bc{{$}}
+NONE-NEXT: {{^}}f2.a(f2.o at [[#F2_OFFSET]]).2.[[#%X,HEXPID]].o{{$}}
+NONE-NOT: {{^f[1-3]}}
+
+# 2. All of the backend compilations hit in the ThinLTO cache.
+#
+# Both f1.o and f2.o are satisfied from the ThinLTO cache. No backend
+# compilation runs, so there should be no .native.o files, no .thinlto.bc files,
+# and no serialized archive-member bitcode files.
+RUN: mkdir all
+RUN: %{link} -Wl,--whole-archive f1.a f2.a -o all/out.elf
+RUN: ls all | sort | FileCheck %s --check-prefix=ALL
+ALL-NOT: {{^f[1-3]}}
+
+# 3. Some backend compilations hit in the ThinLTO cache and some miss.
+#
+# f1.o and f2.o are cached, but the new f3.o misses the cache and imports
+# from f1.o:
+# * f3.o must be compiled and serialized
+# * cached f1.o must be serialized because f3.o imports from it.
+# * cached f2.o should not be serialized or compiled.
+RUN: %clang --target=x86_64-linux-gnu -O2 -flto=thin -c f3.c
+RUN: llvm-ar rcs f3.a f3.o
+RUN: mkdir some
+RUN: %{link} -Wl,--whole-archive f1.a f2.a f3.a -o some/out.elf
+RUN: ls some | sort | FileCheck %s --check-prefix=SOME
+SOME-NOT: {{^f[1-3]}}
+SOME: {{^}}f1.a(f1.o at [[#F1_OFFSET:]]).1.[[#%X,HEXPID:]].o{{$}}
+SOME-NEXT: {{^}}f3.a(f3.o at [[#F3_OFFSET:]]).3.[[#%X,HEXPID]].3.[[#PID:]].native.o{{$}}
+SOME-NEXT: {{^}}f3.a(f3.o at [[#F3_OFFSET]]).3.[[#%X,HEXPID]].3.[[#PID]].native.o.thinlto.bc{{$}}
+SOME-NEXT: {{^}}f3.a(f3.o at [[#F3_OFFSET]]).3.[[#%X,HEXPID]].o{{$}}
+SOME-NOT: {{^f[1-3]}}
+
+#--- f1.c
+__attribute__((retain)) int f1(int x) { return x; }
+
+#--- f2.c
+extern int f1(int);
+__attribute__((retain)) int f2(int x) { return f1(x) + x; }
+
+#--- f3.c
+extern int f1(int);
+__attribute__((retain)) int f3(int x) { return f1(x) + x; }
diff --git a/llvm/include/llvm/DTLTO/DTLTO.h b/llvm/include/llvm/DTLTO/DTLTO.h
index 54e4a414dae2a..6b09b0e4794d9 100644
--- a/llvm/include/llvm/DTLTO/DTLTO.h
+++ b/llvm/include/llvm/DTLTO/DTLTO.h
@@ -16,6 +16,7 @@
#ifndef LLVM_DTLTO_DTLTO_H
#define LLVM_DTLTO_DTLTO_H
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Support/ManagedStatic.h"
@@ -38,11 +39,11 @@ namespace lto {
/// to DTLTO distributors.
///
/// Input files are kept until the pipeline has determined per-module ThinLTO
-/// participation. addInput() performs: (1) register the input; (2) on Windows,
-/// normalize module ID for standalone bitcode; (3) for thin archive members,
-/// set module ID to the on-disk member path; (4) for other archives and FatLTO,
-/// set module ID to a unique path and serialize content in
-/// serializeLTOInputs().
+/// participation and cache status. addInput() performs: (1) register the
+/// input; (2) on Windows, normalize module ID for standalone bitcode; (3) for
+/// thin archive members, set module ID to the on-disk member path; (4) for
+/// other archives and FatLTO, set module ID to a unique path whose content is
+/// serialized later by serializeLTOInputs().
class LLVM_ABI DTLTO : public LTO {
using Base = LTO;
@@ -98,16 +99,17 @@ class LLVM_ABI DTLTO : public LTO {
virtual Error run(AddStreamFn AddStream, FileCache Cache = {}) override;
private:
- /// DTLTO archives support.
+ /// DTLTO archive support.
///
/// Save the contents of ThinLTO-enabled input files that must be serialized
/// for distribution, such as archive members and FatLTO objects, to
/// individual bitcode files named after the module ID.
///
- /// Must be called after all input files are added but before optimization
- /// begins. If a file with that name already exists, it is likely a leftover
- /// from a previously terminated linker process and can be safely overwritten.
- Error serializeLTOInputs();
+ /// Must be called after all input files are added and cache hits are known,
+ /// but before optimization begins. Existing files are overwritten because
+ /// they are likely leftovers from a previously terminated linker process and
+ /// can be safely replaced.
+ LLVM_ABI Error serializeLTOInputs();
// Remove temporary files created to enable distribution.
void cleanup() override;
@@ -163,6 +165,8 @@ class LLVM_ABI DTLTO : public LTO {
private:
// Backend compilation jobs, one per module.
SmallVector<Job> Jobs;
+ // Input module IDs that must be serialized to individual files.
+ DenseSet<StringRef> InputModuleIDsToSerialize;
// Task index offset for first ThinLTO job.
unsigned ThinLTOTaskOffset;
// Optional cache for native objects.
@@ -210,10 +214,13 @@ class LLVM_ABI DTLTO : public LTO {
/// distributor will skip this job. On a cache miss, J.CacheAddStream is
/// set for later use when storing the compiled object.
///
- /// 4. Writes the per-module summary index to disk only on cache miss. The
+ /// 4. Records the module ID and imported module IDs that must be serialized
+ /// to individual files.
+ ///
+ /// 5. Writes the per-module summary index to disk only on cache miss. The
/// remote compiler will read this via -fthinlto-index=.
///
- /// 5. Registers the job's temporary files for removal on abnormal process
+ /// 6. Registers the job's temporary files for removal on abnormal process
/// exit when SaveTemps is false (only for files that will be created).
///
/// \param ModulePath The module identifier (bitcode path) for the ThinLTO
diff --git a/llvm/lib/DTLTO/DTLTO.cpp b/llvm/lib/DTLTO/DTLTO.cpp
index 162f4a60ab9f9..6805746f64978 100644
--- a/llvm/lib/DTLTO/DTLTO.cpp
+++ b/llvm/lib/DTLTO/DTLTO.cpp
@@ -154,6 +154,10 @@ Error lto::DTLTO::prepareDtltoJob(StringRef ModulePath, unsigned Task) {
if (Error Err = checkCacheHit(J))
return Err;
if (!J.Cached) {
+ InputModuleIDsToSerialize.insert(J.ModuleID);
+ for (StringRef ImportPath : J.ImportsFilesList)
+ InputModuleIDsToSerialize.insert(ImportPath);
+
TimeTraceScope JobScope("Emit individual index for DTLTO",
J.SummaryIndexPath);
if (Error Err = save(SummaryIndexFiles[Task], J.SummaryIndexPath))
@@ -225,6 +229,8 @@ Error lto::DTLTO::prepareDtltoJobs() {
auto &ModuleMap =
ThinLTO.ModulesToCompile ? *ThinLTO.ModulesToCompile : ThinLTO.ModuleMap;
+ InputModuleIDsToSerialize.clear();
+
if (ModuleMap.empty())
return Error::success();
diff --git a/llvm/lib/DTLTO/DTLTOInputFiles.cpp b/llvm/lib/DTLTO/DTLTOInputFiles.cpp
index 8751cb14d7a06..c89fbe4575f6a 100644
--- a/llvm/lib/DTLTO/DTLTOInputFiles.cpp
+++ b/llvm/lib/DTLTO/DTLTOInputFiles.cpp
@@ -207,15 +207,18 @@ Error lto::DTLTO::serializeLTOInputs() {
for (auto &Input : InputFiles) {
if (!Input->isThinLTO() || !Input->getSerializeForDistribution())
continue;
+
// Save the content of the input file to a file named after the module ID.
- StringRef ModuleId = Input->getName();
- TimeTraceScope TimeScope("Serialize bitcode input for DTLTO", ModuleId);
+ StringRef ModuleID = Input->getName();
+ if (!InputModuleIDsToSerialize.contains(ModuleID))
+ continue;
+ TimeTraceScope TimeScope("Serialize bitcode input for DTLTO", ModuleID);
MemoryBufferRef Buf = Input->getFileBuffer();
- if (Error Err = save(Buf.getBuffer(), ModuleId))
+ if (Error Err = save(Buf.getBuffer(), ModuleID))
return Err;
// Cleanup this file on abnormal process exit.
if (!SaveTemps)
- addToCleanup(ModuleId);
+ addToCleanup(ModuleID);
}
return Error::success();
}
More information about the llvm-commits
mailing list