diff --git a/cross-project-tests/dtlto/cache-serialization.test b/cross-project-tests/dtlto/cache-serialization.test new file mode 100644 index 000000000000..9be19a4b9495 --- /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 54e4a414dae2..6b09b0e4794d 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 @@ public: 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 @@ public: private: // Backend compilation jobs, one per module. SmallVector Jobs; + // Input module IDs that must be serialized to individual files. + DenseSet InputModuleIDsToSerialize; // Task index offset for first ThinLTO job. unsigned ThinLTOTaskOffset; // Optional cache for native objects. @@ -210,10 +214,13 @@ private: /// 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 162f4a60ab9f..6805746f6497 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 8751cb14d7a0..c89fbe4575f6 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(); }