[lld] [llvm] [DTLTO] Refactor the DTLTO code. (PR #192629)
Konstantin Belochapka via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 04:05:02 PDT 2026
https://github.com/kbelochapka created https://github.com/llvm/llvm-project/pull/192629
DTLTO implementation code has been moved from `llvm/lib/LTO/` to `llvm/lib/DTLTO/`. This refactor does not change any externally visible behavior, so existing DTLTO tests and documentation remain valid. The move was done to reduce code duplication, improve maintainability, and make it easier to adopt future performance improvements.
>From dbc1e81a28eb0a73d2eedcf0617e2201238df9ba Mon Sep 17 00:00:00 2001
From: Konstantin Belochapka <konstantin.belochapka at sony.com>
Date: Tue, 3 Mar 2026 00:58:28 -0800
Subject: [PATCH] [DTLTO] Refactor the DTLTO code.
DTLTO implementation code has been moved from `llvm/lib/LTO/` to `llvm/lib/DTLTO/`.
This refactor does not change any externally visible behavior,
so existing DTLTO tests and documentation remain valid.
The move was done to reduce code duplication, improve maintainability,
and make it easier to adopt future performance improvements.
---
cross-project-tests/dtlto/dtlto-cache.test | 4 +-
cross-project-tests/dtlto/savetemps-lock.test | 4 +-
lld/COFF/LTO.cpp | 19 +-
lld/ELF/LTO.cpp | 17 +-
lld/test/ELF/dtlto/timetrace.test | 4 +-
llvm/include/llvm/DTLTO/DTLTO.h | 399 +++++++++++--
llvm/include/llvm/LTO/Config.h | 17 +
llvm/include/llvm/LTO/LTO.h | 55 +-
llvm/lib/DTLTO/DTLTO.cpp | 444 ++++++++++++--
llvm/lib/LTO/LTO.cpp | 552 +++---------------
llvm/test/ThinLTO/X86/dtlto/timetrace.ll | 6 +
llvm/tools/llvm-lto2/CMakeLists.txt | 1 +
llvm/tools/llvm-lto2/llvm-lto2.cpp | 29 +-
13 files changed, 907 insertions(+), 644 deletions(-)
diff --git a/cross-project-tests/dtlto/dtlto-cache.test b/cross-project-tests/dtlto/dtlto-cache.test
index 5dd67a50ab2c3..82c13eb80dca0 100644
--- a/cross-project-tests/dtlto/dtlto-cache.test
+++ b/cross-project-tests/dtlto/dtlto-cache.test
@@ -31,8 +31,8 @@ RUN: -Wl,--thinlto-remote-compiler=%clang \
RUN: -Wl,--thinlto-cache-dir=cache.dir \
RUN: -Wl,--save-temps
-# Check that there are no backend compilation jobs occurred.
-RUN: grep -wo args populate2.*.dist-file.json | wc -l | grep -qx "\s*1"
+# At this point we expect full cache hits. Distributor will not be invoked and a Json file will not be created.
+RUN: not ls cache.dir/*.dist-file.json
RUN: ls cache.dir | count 3
RUN: %clang -O0 --target=x86_64-linux-gnu -flto=thin -c foo.c -o foo.O0.o
diff --git a/cross-project-tests/dtlto/savetemps-lock.test b/cross-project-tests/dtlto/savetemps-lock.test
index 822744b1dfc4f..17ada42a5ef14 100644
--- a/cross-project-tests/dtlto/savetemps-lock.test
+++ b/cross-project-tests/dtlto/savetemps-lock.test
@@ -30,12 +30,12 @@ RUN: | FileCheck %s --implicit-check-not=warning
CHECK-DAG: Lock any files in the output directory.
CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.[[#PID:]].dist-file.json': {{.*}}
# Filename composition: <archive>(<member> at <offset>).<task>.<pid>.<task>.<pid>.native.o.
+CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].o': {{.*}}
CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET:]]).1.[[#%X,HEXPID:]].1.[[#PID]].native.o': {{.*}}
CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].1.[[#PID]].native.o.thinlto.bc': {{.*}}
+CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].o': {{.*}}
CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET:]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o': {{.*}}
CHECK-DAG: warning: could not remove the file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].2.[[#PID]].native.o.thinlto.bc': {{.*}}
-CHECK-DAG: warning: could not remove temporary DTLTO input file 'locked{{/|\\}}t.a(t1.o at [[#T1_OFFSET]]).1.[[#%X,HEXPID]].o': {{.*}}
-CHECK-DAG: warning: could not remove temporary DTLTO input file 'locked{{/|\\}}t.a(t2.o at [[#T2_OFFSET]]).2.[[#%X,HEXPID]].o': {{.*}}
#--- t1.c
__attribute__((retain)) int t1(int x) { return x; }
diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp
index 445ea52e995da..53a8cd2eb3210 100644
--- a/lld/COFF/LTO.cpp
+++ b/lld/COFF/LTO.cpp
@@ -126,17 +126,7 @@ BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) {
// Initialize ltoObj.
lto::ThinBackend backend;
- if (!ctx.config.dtltoDistributor.empty()) {
- backend = lto::createOutOfProcessThinBackend(
- llvm::hardware_concurrency(ctx.config.thinLTOJobs),
- /*OnWrite=*/nullptr,
- /*ShouldEmitIndexFiles=*/false,
- /*ShouldEmitImportFiles=*/false, ctx.config.outputFile,
- ctx.config.dtltoDistributor, ctx.config.dtltoDistributorArgs,
- ctx.config.dtltoCompiler, ctx.config.dtltoCompilerPrependArgs,
- ctx.config.dtltoCompilerArgs, !ctx.config.saveTempsArgs.empty(),
- createAddBufferFn(files, file_names));
- } else if (ctx.config.thinLTOIndexOnly) {
+ if (ctx.config.thinLTOIndexOnly || !ctx.config.dtltoDistributor.empty()) {
auto OnIndexWrite = [&](StringRef S) { thinIndices.erase(S); };
backend = lto::createWriteIndexesThinBackend(
llvm::hardware_concurrency(ctx.config.thinLTOJobs),
@@ -155,7 +145,12 @@ BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) {
else
ltoObj = std::make_unique<lto::DTLTO>(
createConfig(), backend, ctx.config.ltoPartitions,
- llvm::lto::LTO::LTOKind::LTOK_Default, ctx.config.outputFile,
+ llvm::lto::LTO::LTOKind::LTOK_Default, nullptr,
+ ctx.config.thinLTOEmitImportsFiles, ctx.config.thinLTOIndexOnly,
+ ctx.config.outputFile, ctx.config.dtltoDistributor,
+ ctx.config.dtltoDistributorArgs, ctx.config.dtltoCompiler,
+ ctx.config.dtltoCompilerPrependArgs, ctx.config.dtltoCompilerArgs,
+ createAddBufferFn(files, file_names),
!ctx.config.saveTempsArgs.empty());
}
diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index 7b0fe2001439e..d87e83f8a1f38 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -184,21 +184,13 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) {
// Initialize ltoObj.
lto::ThinBackend backend;
auto onIndexWrite = [&](StringRef s) { thinIndices.erase(s); };
- if (ctx.arg.thinLTOIndexOnly) {
+ if (ctx.arg.thinLTOIndexOnly || !ctx.arg.dtltoDistributor.empty()) {
backend = lto::createWriteIndexesThinBackend(
llvm::hardware_concurrency(ctx.arg.thinLTOJobs),
std::string(ctx.arg.thinLTOPrefixReplaceOld),
std::string(ctx.arg.thinLTOPrefixReplaceNew),
std::string(ctx.arg.thinLTOPrefixReplaceNativeObject),
ctx.arg.thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite);
- } else if (!ctx.arg.dtltoDistributor.empty()) {
- backend = lto::createOutOfProcessThinBackend(
- llvm::hardware_concurrency(ctx.arg.thinLTOJobs), onIndexWrite,
- ctx.arg.thinLTOEmitIndexFiles, ctx.arg.thinLTOEmitImportsFiles,
- ctx.arg.outputFile, ctx.arg.dtltoDistributor,
- ctx.arg.dtltoDistributorArgs, ctx.arg.dtltoCompiler,
- ctx.arg.dtltoCompilerPrependArgs, ctx.arg.dtltoCompilerArgs,
- !ctx.arg.saveTempsArgs.empty(), createAddBufferFn(files, filenames));
} else {
backend = lto::createInProcessThinBackend(
llvm::heavyweight_hardware_concurrency(ctx.arg.thinLTOJobs),
@@ -210,6 +202,7 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) {
llvm::lto::LTO::LTOKind::LTOK_UnifiedThin,
llvm::lto::LTO::LTOKind::LTOK_UnifiedRegular,
llvm::lto::LTO::LTOKind::LTOK_Default};
+
if (ctx.arg.dtltoDistributor.empty())
ltoObj = std::make_unique<lto::LTO>(createConfig(ctx), backend,
ctx.arg.ltoPartitions,
@@ -217,7 +210,11 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) {
else
ltoObj = std::make_unique<lto::DTLTO>(
createConfig(ctx), backend, ctx.arg.ltoPartitions,
- ltoModes[ctx.arg.ltoKind], ctx.arg.outputFile,
+ ltoModes[ctx.arg.ltoKind], onIndexWrite, ctx.arg.thinLTOEmitIndexFiles,
+ ctx.arg.thinLTOEmitImportsFiles, ctx.arg.outputFile,
+ ctx.arg.dtltoDistributor, ctx.arg.dtltoDistributorArgs,
+ ctx.arg.dtltoCompiler, ctx.arg.dtltoCompilerPrependArgs,
+ ctx.arg.dtltoCompilerArgs, createAddBufferFn(files, filenames),
!ctx.arg.saveTempsArgs.empty());
// Initialize usedStartStop.
if (ctx.bitcodeFiles.empty())
diff --git a/lld/test/ELF/dtlto/timetrace.test b/lld/test/ELF/dtlto/timetrace.test
index 4567b0a1d4b02..f43f51284b5c4 100644
--- a/lld/test/ELF/dtlto/timetrace.test
+++ b/lld/test/ELF/dtlto/timetrace.test
@@ -32,12 +32,12 @@ RUN: %python filter_order_and_pprint.py %t.json | FileCheck %s
## Check that DTLTO add input file events are recorded.
CHECK: "name": "Add input for DTLTO"
CHECK: "name": "Add input for DTLTO"
-CHECK: "name": "Remove temporary inputs for DTLTO"
+CHECK: "name": "Remove DTLTO temporary files"
CHECK: "name": "Serialize bitcode input for DTLTO"
CHECK-SAME: "detail": "t1.a(t1.bc at [[#ARCHIVE_OFFSET:]]).1.[[PID:[A-F0-9]+]].o"
CHECK: "name": "Total Add input for DTLTO"
CHECK-SAME: "count": 2,
-CHECK: "name": "Total Remove temporary inputs for DTLTO"
+CHECK: "name": "Total Remove DTLTO temporary files"
CHECK-SAME: "count": 1,
CHECK: "name": "Total Serialize bitcode input for DTLTO"
CHECK-SAME: "count": 1,
diff --git a/llvm/include/llvm/DTLTO/DTLTO.h b/llvm/include/llvm/DTLTO/DTLTO.h
index 5a8566b71bc16..248b86674e6bd 100644
--- a/llvm/include/llvm/DTLTO/DTLTO.h
+++ b/llvm/include/llvm/DTLTO/DTLTO.h
@@ -1,83 +1,402 @@
-//===- DTLTO.h - Distributed ThinLTO functions and classes ----*- C++ -*-===//
+//===- DTLTO.h - Distributed ThinLTO implementation -----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
-//===---------------------------------------------------------------------===//
+//===----------------------------------------------------------------------===//
+//
+// \file
+// Declarations for Distributed ThinLTO, including the DTLTO class and the
+// distribution driver. The implementation focuses on preparing input files for
+// distribution.
+//
+//===----------------------------------------------------------------------===//
#ifndef LLVM_DTLTO_DTLTO_H
#define LLVM_DTLTO_DTLTO_H
+#include "llvm/ADT/SmallString.h"
#include "llvm/LTO/LTO.h"
-#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/ManagedStatic.h"
+#include "llvm/Support/Signals.h"
+
+#include <functional>
+#include <vector>
namespace llvm {
namespace lto {
-// The purpose of this class is to prepare inputs so that distributed ThinLTO
-// backend compilations can succeed.
-//
-// For distributed compilation, each input must exist as an individual bitcode
-// file on disk and be loadable via its ModuleID. This requirement is not met
-// for archive members, as an archive is a collection of files rather than a
-// standalone file. Similarly, for FatLTO objects, the bitcode is stored in a
-// section of the containing ELF object file. To address this, the class ensures
-// that an individual bitcode file exists for each input (by writing it out if
-// necessary) and that the ModuleID is updated to point to it. Module IDs are
-// also normalized on Windows to remove short 8.3 form paths that cannot be
-// loaded on remote machines.
-//
-// The class ensures that lto::InputFile objects are preserved until enough of
-// the LTO pipeline has executed to determine the required per-module
-// information, such as whether a module will participate in ThinLTO.
+class DTLTO;
+
+/// Prepares inputs for Distributed ThinLTO so that backend compilations can use
+/// individual bitcode paths and consistent module IDs.
+///
+/// Each input must exist as an individual bitcode file on disk and be loadable
+/// via its ModuleID. Archive members and FatLTO objects do not satisfy that by
+/// default; this class writes bitcode out when needed and updates ModuleID.
+/// On Windows, module IDs are normalized to remove short 8.3 path components
+/// that are machine-local and break distribution; other normalization is left
+/// 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
+/// handleArchiveInputs().
class DTLTO : public LTO {
using Base = LTO;
public:
LLVM_ABI DTLTO(Config Conf, ThinBackend Backend,
unsigned ParallelCodeGenParallelismLevel, LTOKind LTOMode,
- StringRef LinkerOutputFile, bool SaveTemps)
+ IndexWriteCallback OnWrite, bool EmitIndexFiles,
+ bool EmitImportsFiles, StringRef LinkerOutputFile,
+ StringRef Distributor, ArrayRef<StringRef> DistributorArgs,
+ StringRef RemoteCompiler,
+ ArrayRef<StringRef> RemoteCompilerPrependArgs,
+ ArrayRef<StringRef> RemoteCompilerArgs,
+ AddBufferFn AddBufferArg, bool SaveTempsArg)
: Base(std::move(Conf), Backend, ParallelCodeGenParallelismLevel,
LTOMode),
- LinkerOutputFile(LinkerOutputFile), SaveTemps(SaveTemps) {
+ OnWriteCb(OnWrite), ShouldEmitIndexFiles(EmitIndexFiles),
+ ShouldEmitImportFiles(EmitImportsFiles),
+ DistributorParams{Distributor, DistributorArgs,
+ RemoteCompiler, RemoteCompilerPrependArgs,
+ RemoteCompilerArgs, LinkerOutputFile},
+ AddBuffer(AddBufferArg), SaveTemps(SaveTempsArg) {
assert(!LinkerOutputFile.empty() && "expected a valid linker output file");
}
- // Add an input file and prepare it for distribution.
+ /// Add an input file and prepare it for distribution.
+ ///
+ /// This function performs the following tasks:
+ /// 1. Add the input file to the LTO object's list of input files.
+ /// 2. For individual bitcode file inputs on Windows only, overwrite the
+ /// module
+ /// ID with a normalized path to remove short 8.3 form components.
+ /// 3. For thin archive members, overwrite the module ID with the path
+ /// (normalized on Windows) to the member file on disk.
+ /// 4. For archive members and FatLTO objects, overwrite the module ID with a
+ /// unique path (normalized on Windows) naming a file that will contain the
+ /// member content. The file is created and populated later (see
+ /// serializeInputs()).
LLVM_ABI Expected<std::shared_ptr<InputFile>>
addInput(std::unique_ptr<InputFile> InputPtr) override;
-protected:
- // 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.
- LLVM_ABI llvm::Error serializeInputsForDistribution() override;
+ /// Runs the DTLTO pipeline. This function calls the supplied AddStream
+ /// function to add native object files to the link.
+ ///
+ /// The Cache parameter is optional. If supplied, it will be used to cache
+ /// native object files and add them to the link.
+ ///
+ /// The client will receive at most one callback (via either AddStream or
+ /// Cache) for each task identifier.
+ LLVM_ABI virtual Error run(AddStreamFn AddStream,
+ FileCache Cache = {}) override;
+
+private:
+ /// DTLTO archives 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.
+ LLVM_ABI Error handleArchiveInputs();
+
+ // Remove temporary files created to enable distribution.
+ LLVM_ABI void cleanup();
+
+public:
+ // Mutable and const accessors to the LTO configuration object.
+ Config &getConfig() { return Conf; }
+ const Config &getConfig() const { return Conf; }
- LLVM_ABI void cleanup() override;
+ // Set the LTO kind.
+ void setLTOMode(LTOKind Knd) { LTOMode = Knd; }
+ // Replace the ThinLTO backend (e.g. WriteIndexesThinBackend for the thin
+ // link).
+ void setThinBackend(ThinBackend Backend) { ThinLTO.Backend = Backend; }
private:
- // Bump allocator for a purpose of saving updated module IDs.
+ // Bump allocator for saving updated module IDs.
BumpPtrAllocator PtrAlloc;
+ // String saver backed by PtrAlloc.
StringSaver Saver{PtrAlloc};
- /// The output file to which this LTO invocation will contribute.
- StringRef LinkerOutputFile;
+ using SString = SmallString<128>;
- /// The normalized output directory, derived from LinkerOutputFile.
- StringRef LinkerOutputDir;
+ // Function pointer that defines the callback to add a pre-existing file.
+ AddBufferFn AddBuffer;
+ // Count of jobs that hit the cache.
+ std::atomic<size_t> CachedJobs{0};
+ // Normalized output directory from LinkerOutputFile.
+ SString LinkerOutputDir;
+ // Keep temporary files when true.
+ bool SaveTemps = false;
- /// Controls preservation of any created temporary files.
- bool SaveTemps;
+public:
+ struct Job {
+ // Task index (combines RegularLTO parallel codegen offset with module
+ // index).
+ unsigned Task;
+ // Module identifier (bitcode path) for the ThinLTO module.
+ StringRef ModuleID;
+ // Native object path.
+ StringRef NativeObjectPath;
+ // Per-module summary index path.
+ StringRef SummaryIndexPath;
+ // Per-module imports list path.
+ StringRef ImportsPath;
+ // Bitcode files from which this module imports.
+ ArrayRef<std::string> ImportsFiles;
+ // Cache key from thin link.
+ std::string CacheKey;
+ // On cache miss, stream used to store the compiled object in the cache.
+ AddStreamFn CacheAddStream;
+ // Set when the object was already supplied via the cache callback.
+ bool Cached = false;
+ };
- // Array of input bitcode files for LTO.
- std::vector<std::shared_ptr<lto::InputFile>> InputFiles;
+private:
+ // Backend compilation jobs, one per module.
+ SmallVector<Job> Jobs;
+ // Task index offset for first ThinLTO job.
+ unsigned ThinLTOTaskOffset;
+ // Optional cache for native objects.
+ FileCache Cache;
+ // Keep summary index files when true.
+ bool ShouldEmitIndexFiles = false;
+ // Keep summary import files when true.
+ bool ShouldEmitImportFiles = false;
+ // On index file write callback.
+ IndexWriteCallback OnWriteCb;
- // Cache of whether a path refers to a thin archive.
- StringMap<bool> ArchiveIsThinCache;
+ /// Probes the LTO cache for a compiled native object for the given job.
+ ///
+ /// If no cache is configured (Cache.isValid() is false), returns immediately
+ /// without modifying the job.
+ ///
+ /// Otherwise, looks up the cache using J.CacheKey. On a cache hit, the cached
+ /// object has already been passed to the linker via the Cache callback, so
+ /// J.Cached is set to true, CachedJobs is incremented, and the distributor
+ /// can skip this job. On a cache miss, the cache returns an AddStreamFn; we
+ /// store it in J.CacheAddStream for use when storing the freshly compiled
+ /// object after the distributor runs.
+ ///
+ /// \param J The job to check. Must have Task, CacheKey, and ModuleID set.
+ /// On return, J.Cached and J.CacheAddStream may be updated.
+ ///
+ /// \returns Error::success() on success, or an Error from the cache lookup.
+ Error checkCacheHit(Job &J);
+
+ /// Prepares a single DTLTO backend compilation job for a ThinLTO module.
+ ///
+ /// Called once per module during performCodegen(). This function:
+ ///
+ /// 1. Computes output paths for the native object and summary index files.
+ /// Both are placed in the linker output directory with names of the form
+ /// stem.Task.UID.native.o and stem.Task.UID.thinlto.bc, where stem is
+ /// derived from ModulePath.
+ ///
+ /// 2. Initializes the Job struct with Task, ModuleID (ModulePath), paths,
+ /// ImportsFiles and CacheKey from thin link results, and default values
+ /// for CacheAddStream and Cached.
+ ///
+ /// 3. Calls checkCacheHit() to probe the cache. On a cache hit, J.Cached is
+ /// set and the cached object has already been passed to the linker; the
+ /// 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
+ /// remote compiler will read this via -fthinlto-index=.
+ ///
+ /// 5. 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
+ /// module.
+ /// \param Task The task index (combines RegularLTO.ParallelCodeGen
+ /// parallelism offset with the module index).
+ ///
+ /// \returns Error::success() on success, or an Error from saveBuffer() or
+ /// checkCacheHit().
+ Error prepareDtltoJob(StringRef ModulePath, unsigned Task);
+
+ /// Initializes DTLTO state and prepares a job for each ThinLTO module.
+ ///
+ /// Sets task offset, target triple, UID, and Jobs. For each module, calls
+ /// prepareDtltoJob() to assign output paths, check the cache, and write
+ /// summary index shards to disk when needed.
+ ///
+ /// \returns Error::success() on success, or an Error from prepareDtltoJob.
+ Error prepareDtltoJobs();
- // Determines if the file at the given path is a thin archive.
+ /// Runs the DTLTO code generation phase. Must be invoked after thinLink().
+ ///
+ /// Builds Clang options, emits a JSON manifest describing compilation jobs,
+ /// and invokes the distributor to compile ThinLTO modules remotely. Cache
+ /// hits are skipped; the distributor runs only when there are uncached jobs.
+ ///
+ /// \returns Error::success() on success, or an Error on manifest or
+ /// distributor failure.
+ Error performCodegen();
+
+ /// Adds compiled object files to the link for each non-cached job.
+ ///
+ /// Loads each native object from disk, then either writes it to the cache
+ /// (which adds it to the link via the cache callback) or passes it to
+ /// AddStreamFunc directly when caching is disabled.
+ ///
+ /// \returns Error::success() on success, or an Error if a file cannot be read
+ /// or a cache stream cannot be obtained.
+ Error addObjectFilesToLink();
+
+ // Determines if a file at the given path is a thin archive file.
+ //
+ // Uses a cache to avoid repeatedly reading the same file; reads only the
+ // header (magic bytes) to identify the archive type.
Expected<bool> isThinArchive(const StringRef ArchivePath);
+
+ // Unique ID for this link (process ID as string).
+ std::string UID;
+
+ // Input files registered for this link (same order as addInput).
+ std::vector<std::shared_ptr<lto::InputFile>> InputFiles;
+ // Cache for isThinArchive() results keyed by archive path.
+ StringMap<bool> ArchiveIsThinCache;
+ // Callback used by run() to add native objects to the link.
+ AddStreamFn AddStreamFunc = nullptr;
+ // Per-task summary index shards from the thin link (in-memory buffers).
+ std::vector<SmallString<0>> SummaryIndexFiles;
+ // Per-task imported bitcode paths from the thin link.
+ std::vector<std::vector<std::string>> ImportsFilesLists;
+ // Per-task cache keys for incremental builds from the thin link.
+ std::vector<std::string> CacheKeysList;
+
+ /// Runs the DTLTO thin link phase, producing per-module summary indices,
+ /// import lists, and cache keys for distribution.
+ ///
+ /// This function configures a WriteIndexesThinBackend and invokes the base
+ /// LTO run, which performs the thin link. The thin link resolves cross-module
+ /// references and produces:
+ ///
+ /// - SummaryIndexFiles: per-module summary index shards (in-memory buffers)
+ /// - ImportsFilesLists: per-module lists of imported bitcode files
+ /// - CacheKeysList: per-module cache keys for incremental builds
+ /// - ModuleNames: per-module identifiers
+ ///
+ /// The Config callbacks (GetSummaryIndexStreamFunc, GetCacheKeysListRefFunc,
+ /// GetImportsListRefFunc) are installed so the WriteIndexesThinBackend
+ /// populates these arrays. performCodegen() later uses them to prepare
+ /// backend jobs.
+ ///
+ /// \returns Error::success() if the thin link completes, or an Error from
+ /// Base::run().
+ Error performThinLink();
+
+ /// Derive a set of Clang options that will be shared/common for all DTLTO
+ /// backend compilations. We are intentionally minimal here as these options
+ /// must remain synchronized with the behavior of Clang. DTLTO does not
+ /// support all the features available with in-process LTO. More features are
+ /// expected to be added over time. Users can specify Clang options directly
+ /// if a feature is not supported. Note that explicitly specified options that
+ /// imply additional input or output file dependencies must be communicated to
+ /// the distribution system, potentially by setting extra options on the
+ /// distributor program.
+ void buildCommonRemoteCompilerOptions();
+
+public:
+ // Parameters and shared state for DistributorDriver class.
+ struct DistributionDriverParams {
+ LLVM_ABI
+ DistributionDriverParams() = default;
+ DistributionDriverParams(StringRef DistributorArg,
+ ArrayRef<StringRef> DistributorArgsArg,
+ StringRef RemoteCompilerArg,
+ ArrayRef<StringRef> RemoteCompilerPrependArgsArg,
+ ArrayRef<StringRef> RemoteCompilerArgsArg,
+ StringRef LinkerOutputFileArg)
+ : DistributorPath(DistributorArg), DistributorArgs(DistributorArgsArg),
+ RemoteCompiler(RemoteCompilerArg),
+ RemoteCompilerPrependArgs(RemoteCompilerPrependArgsArg),
+ RemoteCompilerArgs(RemoteCompilerArgsArg),
+ LinkerOutputFile(LinkerOutputFileArg) {}
+
+ // Output linker file path.
+ SString LinkerOutputFile;
+ // Path to the distributor executable.
+ SString DistributorPath;
+ // Arguments passed to the distributor.
+ ArrayRef<StringRef> DistributorArgs;
+ // Compiler executabl invoked by the distributor (e.g., Clang).
+ SString RemoteCompiler;
+ // Options prepended to remote compiler args.
+ ArrayRef<StringRef> RemoteCompilerPrependArgs;
+ // User-supplied options passed to remote compiler.
+ ArrayRef<StringRef> RemoteCompilerArgs;
+
+ // Common Clang options for all compilation jobs.
+ SmallVector<StringRef, 0> CodegenOptions;
+ // Input paths shared across compilation jobs.
+ DenseSet<StringRef> CommonInputs;
+ // Target triple for compilations.
+ Triple TargetTriple;
+ };
+
+private:
+ // Distributor configuration class instance.
+ DistributionDriverParams DistributorParams;
+
+ // Cleanup files list.
+ std::vector<std::string> CleanupList;
+
+ // Record a file for cleanup and register signal-time removal if requested.
+ void addToCleanup(StringRef Filename) {
+ CleanupList.push_back(Filename.str());
+ sys::RemoveFileOnSignal(Filename);
+ }
+};
+
+namespace {
+constexpr StringRef BCError = "DTLTO backend compilation: ";
+}
+
+class DistributionDriver {
+public:
+ LLVM_ABI
+ DistributionDriver(DTLTO::DistributionDriverParams &ParamsArg,
+ ArrayRef<DTLTO::Job> JobsArg, bool SaveTempsArg,
+ std::function<void(StringRef)> AddToClenupArg)
+ : Params{ParamsArg}, Jobs{JobsArg}, SaveTemps{SaveTempsArg},
+ AddToCleanup{AddToClenupArg} {};
+
+private:
+ DTLTO::DistributionDriverParams &Params;
+ // Keep temporary files when true.
+ bool SaveTemps = false;
+ std::function<void(StringRef)> AddToCleanup;
+ ArrayRef<DTLTO::Job> Jobs;
+ SmallString<128> DistributorJsonFile;
+
+ // Generates a JSON file describing the compilations
+ Error emitJson();
+ // Saves JSON file on a filesystem.
+ Error saveJson();
+
+public:
+ /// Invokes the distributor to compile bitcode modules remotely.
+ ///
+ /// Runs the distributor with the
+ /// JSON manifest path; the distributor spawns remote compiler processes.
+ ///
+ /// \returns Error::success() on success, or an Error if the distributor
+ /// fails.
+ Error operator()();
};
} // namespace lto
diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h
index 2aeb902bcfccf..33d93c21c755a 100644
--- a/llvm/include/llvm/LTO/Config.h
+++ b/llvm/include/llvm/LTO/Config.h
@@ -292,6 +292,23 @@ struct Config {
LLVM_ABI Error addSaveTemps(std::string OutputFileName,
bool UseInputModulePath = false,
const DenseSet<StringRef> &SaveTempsArgs = {});
+
+ /// Called by WriteIndexesThinBackend when it needs to write a bitcode
+ /// module's summary index. The callback should return a stream to write
+ /// the index into. If the callback returns nullptr, the backend falls back
+ /// to writing the summary index to a file.
+ std::function<std::unique_ptr<raw_pwrite_stream>(size_t Task)>
+ OnSummaryIndexStoreCb;
+ /// Called by WriteIndexesThinBackend when it needs to store a bitcode
+ /// module's imports list. The callback should return a vector that the
+ /// backend will populate with the imported module paths. If not set, the
+ /// backend writes the imports list to a file instead.
+ std::function<std::vector<std::string> &(size_t Task)> OnImportsListStoreCb;
+ /// Called by WriteIndexesThinBackend when it needs to store a bitcode
+ /// module's cache key. The callback should return a string that the backend
+ /// will fill with the computed cache key. If not set, the cache key is
+ /// discarded.
+ std::function<std::string &(size_t Task)> OnCacheKeyStoreCb;
};
struct LTOLLVMDiagnosticHandler : public DiagnosticHandler {
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index aba2661e81c47..0cf51c3eaa351 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -287,16 +287,23 @@ class ThinBackendProc {
virtual bool isSensitiveToInputOrder() { return false; }
// Write sharded indices and (optionally) imports to disk
- LLVM_ABI Error emitFiles(const FunctionImporter::ImportMapTy &ImportList,
+ LLVM_ABI Error emitFiles(const FunctionImporter::ImportMapTy &ImportList, unsigned Task,
StringRef ModulePath,
const std::string &NewModulePath) const;
// Write sharded indices to SummaryPath, (optionally) imports to disk, and
- // (optionally) record imports in ImportsFiles.
+ // (optionally) record imports in ImportsFiles. When cache key parameters are
+ // provided and GetCacheKeysListRefFunc is set, also computes and stores the
+ // LTO cache key for the module.
LLVM_ABI Error emitFiles(
- const FunctionImporter::ImportMapTy &ImportList, StringRef ModulePath,
+ const FunctionImporter::ImportMapTy &ImportList, unsigned Task, StringRef ModulePath,
const std::string &NewModulePath, StringRef SummaryPath,
- std::optional<std::reference_wrapper<ImportsFilesContainer>> ImportsFiles)
+ std::optional<std::reference_wrapper<ImportsFilesContainer>> ImportsFiles,
+ const FunctionImporter::ExportSetTy *ExportList = nullptr,
+ const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes> *ResolvedODR =
+ nullptr,
+ const DenseSet<GlobalValue::GUID> *CfiFunctionDefs = nullptr,
+ const DenseSet<GlobalValue::GUID> *CfiFunctionDecls = nullptr)
const;
};
@@ -351,35 +358,6 @@ LLVM_ABI ThinBackend createInProcessThinBackend(
ThreadPoolStrategy Parallelism, IndexWriteCallback OnWrite = nullptr,
bool ShouldEmitIndexFiles = false, bool ShouldEmitImportsFiles = false);
-/// This ThinBackend generates the index shards and then runs the individual
-/// backend jobs via an external process. It takes the same parameters as the
-/// InProcessThinBackend; however, these parameters only control the behavior
-/// when generating the index files for the modules. Additionally:
-/// LinkerOutputFile is a string that should identify this LTO invocation in
-/// the context of a wider build. It's used for naming to aid the user in
-/// identifying activity related to a specific LTO invocation.
-/// Distributor specifies the path to a process to invoke to manage the backend
-/// job execution.
-/// DistributorArgs specifies a list of arguments to be applied to the
-/// distributor.
-/// RemoteCompiler specifies the path to a Clang executable to be invoked for
-/// the backend jobs.
-/// RemoteCompilerPrependArgs specifies a list of prepend arguments to be
-/// applied to the backend compilations.
-/// RemoteCompilerArgs specifies a list of arguments to be applied to the
-/// backend compilations.
-/// SaveTemps is a debugging tool that prevents temporary files created by this
-/// backend from being cleaned up.
-/// AddBuffer is used to add a pre-existing native object buffer to the link.
-LLVM_ABI ThinBackend createOutOfProcessThinBackend(
- ThreadPoolStrategy Parallelism, IndexWriteCallback OnWrite,
- bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles,
- StringRef LinkerOutputFile, StringRef Distributor,
- ArrayRef<StringRef> DistributorArgs, StringRef RemoteCompiler,
- ArrayRef<StringRef> RemoteCompilerPrependArgs,
- ArrayRef<StringRef> RemoteCompilerArgs, bool SaveTemps,
- AddBufferFn AddBuffer);
-
/// This ThinBackend writes individual module indexes to files, instead of
/// running the individual backend jobs. This backend is for distributed builds
/// where separate processes will invoke the real backends.
@@ -467,7 +445,7 @@ class LTO {
///
/// The client will receive at most one callback (via either AddStream or
/// Cache) for each task identifier.
- LLVM_ABI Error run(AddStreamFn AddStream, FileCache Cache = {});
+ LLVM_ABI virtual Error run(AddStreamFn AddStream, FileCache Cache = {});
/// Static method that returns a list of libcall symbols that can be generated
/// by LTO but might not be visible from bitcode symbol table.
@@ -483,13 +461,9 @@ class LTO {
getLibFuncSymbols(const Triple &TT, llvm::StringSaver &Saver);
protected:
- // Called at the start of run().
- virtual Error serializeInputsForDistribution() { return Error::success(); }
-
// Called before returning from run().
virtual void cleanup();
-private:
Config Conf;
struct RegularLTOState {
@@ -547,6 +521,7 @@ class LTO {
DenseMap<GlobalValue::GUID, StringRef> PrevailingModuleForGUID;
} ThinLTO;
+private:
// The global resolution for a particular (mangled) symbol name. This is in
// particular necessary to track whether each symbol can be internalized.
// Because any input file may introduce a new cross-partition reference, we
@@ -648,9 +623,11 @@ class LTO {
mutable bool CalledGetMaxTasks = false;
+protected:
// LTO mode when using Unified LTO.
LTOKind LTOMode;
+private:
// Use Optional to distinguish false from not yet initialized.
std::optional<bool> EnableSplitLTOUnit;
@@ -687,6 +664,8 @@ class LTO {
addInput(std::unique_ptr<lto::InputFile> InputPtr) {
return std::shared_ptr<lto::InputFile>(InputPtr.release());
}
+
+
};
/// The resolution for a symbol. The linker must provide a SymbolResolution for
diff --git a/llvm/lib/DTLTO/DTLTO.cpp b/llvm/lib/DTLTO/DTLTO.cpp
index b5e233fe0ef54..94bb75c6eb7c1 100644
--- a/llvm/lib/DTLTO/DTLTO.cpp
+++ b/llvm/lib/DTLTO/DTLTO.cpp
@@ -1,4 +1,4 @@
-//===- Dtlto.cpp - Distributed ThinLTO implementation --------------------===//
+//===- DTLTO.cpp - Distributed ThinLTO implementation ---------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -14,6 +14,8 @@
#include "llvm/DTLTO/DTLTO.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
@@ -21,10 +23,10 @@
#include "llvm/LTO/LTO.h"
#include "llvm/Object/Archive.h"
#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBufferRef.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
-#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
#ifdef _WIN32
@@ -67,7 +69,7 @@ Expected<StringRef> normalizePath(StringRef Path, StringSaver &Saver) {
if (Path.empty())
return Path;
SmallString<256> Expanded;
- if (std::error_code EC = llvm::sys::windows::makeLongFormPath(Path, Expanded))
+ if (std::error_code EC = sys::windows::makeLongFormPath(Path, Expanded))
return createStringError(inconvertibleErrorCode(),
"Normalization failed for path %s: %s",
Path.str().c_str(), EC.message().c_str());
@@ -88,8 +90,9 @@ SmallString<256> computeThinArchiveMemberPath(StringRef ArchivePath,
if (sys::path::is_relative(MemberName)) {
MemberPath = sys::path::parent_path(ArchivePath);
sys::path::append(MemberPath, MemberName);
- } else
+ } else {
MemberPath = MemberName;
+ }
sys::path::remove_dots(MemberPath, /*remove_dot_dot=*/true);
return MemberPath;
}
@@ -97,10 +100,6 @@ SmallString<256> computeThinArchiveMemberPath(StringRef ArchivePath,
} // namespace
// Determines if a file at the given path is a thin archive file.
-//
-// This function uses a cache to avoid repeatedly reading the same file.
-// It reads only the header portion (magic bytes) of the file to identify
-// the archive type.
Expected<bool> lto::DTLTO::isThinArchive(const StringRef ArchivePath) {
// Return cached result if available.
auto Cached = ArchiveIsThinCache.find(ArchivePath);
@@ -141,17 +140,6 @@ Expected<bool> lto::DTLTO::isThinArchive(const StringRef ArchivePath) {
}
// Add an input file and prepare it for distribution.
-//
-// This function performs the following tasks:
-// 1. Add the input file to the LTO object's list of input files.
-// 2. For individual bitcode file inputs on Windows only, overwrite the module
-// ID with a normalized path to remove short 8.3 form components.
-// 3. For thin archive members, overwrite the module ID with the path
-// (normalized on Windows) to the member file on disk.
-// 4. For archive members and FatLTO objects, overwrite the module ID with a
-// unique path (normalized on Windows) naming a file that will contain the
-// member content. The file is created and populated later (see
-// serializeInputs()).
Expected<std::shared_ptr<lto::InputFile>>
lto::DTLTO::addInput(std::unique_ptr<InputFile> InputPtr) {
TimeTraceScope TimeScope("Add input for DTLTO");
@@ -205,7 +193,8 @@ lto::DTLTO::addInput(std::unique_ptr<InputFile> InputPtr) {
// Get the normalized output directory, if we haven't already.
if (LinkerOutputDir.empty()) {
- auto N = normalizePath(sys::path::parent_path(LinkerOutputFile), Saver);
+ auto N = normalizePath(
+ sys::path::parent_path(DistributorParams.LinkerOutputFile), Saver);
if (!N)
return N.takeError();
LinkerOutputDir = *N;
@@ -223,43 +212,416 @@ lto::DTLTO::addInput(std::unique_ptr<InputFile> InputPtr) {
}
// 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.
-llvm::Error lto::DTLTO::serializeInputsForDistribution() {
+// distribution.
+Error lto::DTLTO::handleArchiveInputs() {
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);
+ MemoryBufferRef Buf = Input->getFileBuffer();
+ if (Error Err = save(Buf.getBuffer(), ModuleId))
+ return Err;
// Cleanup this file on abnormal process exit.
if (!SaveTemps)
- llvm::sys::RemoveFileOnSignal(ModuleId);
- if (Error EC = save(Input.get(), ModuleId))
- return EC;
+ addToCleanup(ModuleId);
}
-
return Error::success();
}
-// Remove serialized inputs created to enable distribution.
+// Remove temporary files created to enable distribution.
void lto::DTLTO::cleanup() {
if (!SaveTemps) {
- TimeTraceScope TimeScope("Remove temporary inputs for DTLTO");
- for (auto &Input : InputFiles) {
- if (!Input->getSerializeForDistribution())
- continue;
- std::error_code EC =
- sys::fs::remove(Input->getName(), /*IgnoreNonExisting=*/true);
+ // Remove one file, report error if any.
+ auto removeFile = [](StringRef FileName) -> void {
+ std::error_code EC = sys::fs::remove(FileName, true);
if (EC &&
EC != std::make_error_code(std::errc::no_such_file_or_directory))
- errs() << "warning: could not remove temporary DTLTO input file '"
- << Input->getName() << "': " << EC.message() << "\n";
+ errs() << "warning: could not remove the file '" << FileName
+ << "': " << EC.message() << "\n";
+ };
+
+ TimeTraceScope JobScope("Remove DTLTO temporary files");
+ for (const auto &Name : CleanupList) {
+ removeFile(Name);
+ }
+ }
+ // Base::cleanup();
+}
+
+// Runs the DTLTO thin link phase, producing per-module summary indices,
+// import lists, and cache keys for distribution.
+Error lto::DTLTO::performThinLink() {
+ auto ThinIndexBackend = lto::createWriteIndexesThinBackend(
+ hardware_concurrency(), "", "", "", true, nullptr, nullptr);
+ setThinBackend(ThinIndexBackend);
+ setLTOMode(lto::LTO::LTOKind::LTOK_UnifiedThin);
+
+ size_t NumTasks = getMaxTasks();
+ SummaryIndexFiles.resize(NumTasks);
+ ImportsFilesLists.resize(NumTasks);
+ CacheKeysList.resize(NumTasks);
+
+ lto::Config &Cfg = getConfig();
+ Cfg.OnSummaryIndexStoreCb =
+ [&](size_t task) -> std::unique_ptr<raw_svector_ostream> {
+ return std::make_unique<raw_svector_ostream>(SummaryIndexFiles[task]);
+ };
+ Cfg.OnCacheKeyStoreCb = [&](size_t task) -> std::string & {
+ return CacheKeysList[task];
+ };
+ Cfg.OnImportsListStoreCb = [&](size_t task) -> std::vector<std::string> & {
+ return ImportsFilesLists[task];
+ };
+
+ return Base::run(AddStreamFunc, {});
+}
+
+// Runs the DTLTO pipeline.
+LLVM_ABI Error lto::DTLTO::run(AddStreamFn AddStream, FileCache CacheParam) {
+ scope_exit CleanUp([this]() { cleanup(); });
+
+ AddStreamFunc = AddStream;
+ Cache = std::move(CacheParam);
+ Conf.Dtlto = 1;
+ UID = itostr(sys::Process::getProcessId());
+
+ if (Error Err = performThinLink())
+ return Err;
+
+ ThinLTOTaskOffset = RegularLTO.ParallelCodeGenParallelismLevel;
+ DistributorParams.TargetTriple = RegularLTO.CombinedModule->getTargetTriple();
+
+ if (Error Err = prepareDtltoJobs())
+ return Err;
+ if (Error Err = handleArchiveInputs())
+ return Err;
+ if (Error Err = performCodegen())
+ return Err;
+ if (Error Err = addObjectFilesToLink())
+ return Err;
+ return Error::success();
+}
+
+// Probes the LTO cache for a compiled native object for the given job.
+Error lto::DTLTO::checkCacheHit(Job &J) {
+ if (!Cache.isValid())
+ return Error::success();
+
+ auto CacheAddStreamExp = Cache(J.Task, J.CacheKey, J.ModuleID);
+ if (Error Err = CacheAddStreamExp.takeError())
+ return Err;
+ AddStreamFn &CacheAddStream = *CacheAddStreamExp;
+ // If CacheAddStream is null, we have a cache hit and at this point
+ // object file is already passed back to the linker.
+ if (!CacheAddStream) {
+ J.Cached = true; // Cache hit, mark the job as cached.
+ CachedJobs.fetch_add(1);
+ } else {
+ // If CacheAddStream is not null, we have a cache miss and we need to
+ // run the backend for codegen. Save cache 'add stream'
+ // function for a later use.
+ J.CacheAddStream = std::move(CacheAddStream);
+ }
+ return Error::success();
+}
+
+// Prepares a single DTLTO backend compilation job for a ThinLTO module.
+Error lto::DTLTO::prepareDtltoJob(StringRef ModulePath, unsigned Task) {
+ assert(Task >= ThinLTOTaskOffset && Task - ThinLTOTaskOffset < Jobs.size() &&
+ "Task index out of range for Jobs");
+ assert(Task < SummaryIndexFiles.size() && "Task index out of range");
+
+ SString ObjFilePath =
+ sys::path::parent_path(DistributorParams.LinkerOutputFile);
+ sys::path::append(ObjFilePath, sys::path::stem(ModulePath) + "." +
+ itostr(Task) + "." + UID + ".native.o");
+
+ SString SummaryIndexPathStr = ObjFilePath;
+ SummaryIndexPathStr += ".thinlto.bc";
+ SString ImportsPathStr = ModulePath;
+ ImportsPathStr += ".imports";
+
+ Job &J = Jobs[Task - ThinLTOTaskOffset];
+ J = {Task,
+ ModulePath,
+ Saver.save(ObjFilePath.str()),
+ Saver.save(SummaryIndexPathStr.str()),
+ Saver.save(ImportsPathStr.str()),
+ ImportsFilesLists[Task],
+ CacheKeysList[Task],
+ nullptr,
+ false};
+
+ if (Error Err = checkCacheHit(J))
+ return Err;
+ if (!J.Cached) {
+ TimeTraceScope JobScope("Emit individual index for DTLTO",
+ J.SummaryIndexPath);
+ if (Error Err = save(SummaryIndexFiles[Task], J.SummaryIndexPath))
+ return Err;
+ }
+ if (OnWriteCb)
+ OnWriteCb(J.SummaryIndexPath.str());
+
+ if (ShouldEmitImportFiles)
+ if (Error Err = save(join(ImportsFilesLists[Task], "\n"), J.ImportsPath))
+ return Err;
+
+ if (!SaveTemps) {
+ if (!J.Cached)
+ addToCleanup(J.NativeObjectPath.str());
+ if (!ShouldEmitIndexFiles)
+ addToCleanup(J.SummaryIndexPath.str());
+ if (!ShouldEmitImportFiles)
+ addToCleanup(J.ImportsPath.str());
+ }
+ return Error::success();
+}
+
+// Derive a set of Clang options that will be shared/common for all DTLTO
+// backend compilations.
+void lto::DTLTO::buildCommonRemoteCompilerOptions() {
+ const lto::Config &C = getConfig();
+ auto &Ops = DistributorParams.CodegenOptions;
+
+ Ops.push_back(Saver.save("-O" + Twine(C.OptLevel)));
+
+ if (C.Options.EmitAddrsig)
+ Ops.push_back("-faddrsig");
+ if (C.Options.FunctionSections)
+ Ops.push_back("-ffunction-sections");
+ if (C.Options.DataSections)
+ Ops.push_back("-fdata-sections");
+
+ if (C.RelocModel == Reloc::PIC_)
+ // Clang doesn't have -fpic for all triples.
+ if (!DistributorParams.TargetTriple.isOSBinFormatCOFF())
+ Ops.push_back("-fpic");
+
+ // Turn on/off warnings about profile cfg mismatch (default on)
+ // --lto-pgo-warn-mismatch.
+ if (!C.PGOWarnMismatch) {
+ Ops.push_back("-mllvm");
+ Ops.push_back("-no-pgo-warn-mismatch");
+ }
+
+ // Enable sample-based profile guided optimizations.
+ // Sample profile file path --lto-sample-profile=<value>.
+ if (!C.SampleProfile.empty()) {
+ Ops.push_back(Saver.save("-fprofile-sample-use=" + Twine(C.SampleProfile)));
+ DistributorParams.CommonInputs.insert(C.SampleProfile);
+ }
+
+ // We don't know which of options will be used by Clang.
+ Ops.push_back("-Wno-unused-command-line-argument");
+
+ // Forward any supplied options.
+ if (!DistributorParams.RemoteCompilerArgs.empty())
+ for (auto &a : DistributorParams.RemoteCompilerArgs)
+ Ops.push_back(a);
+}
+
+// Initializes DTLTO state and prepares a job for each ThinLTO module.
+Error lto::DTLTO::prepareDtltoJobs() {
+ auto &ModuleMap =
+ ThinLTO.ModulesToCompile ? *ThinLTO.ModulesToCompile : ThinLTO.ModuleMap;
+
+ if (ModuleMap.empty())
+ return Error::success();
+
+ Jobs.resize(ModuleMap.size());
+
+ for (auto [I, Mod] : enumerate(ModuleMap))
+ if (Error E = prepareDtltoJob(Mod.first, ThinLTOTaskOffset + I))
+ return E;
+
+ return Error::success();
+}
+
+// Runs the DTLTO code generation phase. Must be invoked after thinLink().
+Error lto::DTLTO::performCodegen() {
+ if (Jobs.empty())
+ return Error::success();
+ // Build common remote compiler options.
+ buildCommonRemoteCompilerOptions();
+
+ DistributionDriver Distributor(DistributorParams, Jobs, SaveTemps,
+ [&](StringRef S) { addToCleanup(S); });
+
+ if (CachedJobs.load() < Jobs.size()) {
+ if (Error E = Distributor())
+ return E;
+ }
+ return Error::success();
+}
+
+// Adds compiled object files to the link for each non-cached job.
+Error lto::DTLTO::addObjectFilesToLink() {
+ TimeTraceScope FilesScope("Add DTLTO files to the link");
+ for (auto &Job : Jobs) {
+ if (!Job.CacheKey.empty() && Job.Cached) {
+ assert(Cache.isValid());
+ continue;
+ }
+ // Load the native object from a file into a memory buffer
+ // and store its contents in the output buffer.
+ auto ObjFileMbOrErr =
+ MemoryBuffer::getFile(Job.NativeObjectPath, /*IsText=*/false,
+ /*RequiresNullTerminator=*/false);
+ if (std::error_code EC = ObjFileMbOrErr.getError())
+ return make_error<StringError>(
+ BCError + "cannot open native object file: " + Job.NativeObjectPath +
+ ": " + EC.message(),
+ inconvertibleErrorCode());
+
+ MemoryBufferRef ObjFileMbRef = ObjFileMbOrErr->get()->getMemBufferRef();
+ if (Cache.isValid()) {
+ // Cache hits are taken care of earlier. At this point, we could only
+ // have cache misses.
+ assert(Job.CacheAddStream);
+ // Obtain a file stream for a storing a cache entry.
+ auto CachedFileStreamOrErr = Job.CacheAddStream(Job.Task, Job.ModuleID);
+ if (!CachedFileStreamOrErr)
+ return joinErrors(
+ CachedFileStreamOrErr.takeError(),
+ createStringError(inconvertibleErrorCode(),
+ "Cannot get a cache file stream: %s",
+ Job.NativeObjectPath.data()));
+ // Store a file buffer into the cache stream.
+ auto &CacheStream = *(CachedFileStreamOrErr->get());
+ *(CacheStream.OS) << ObjFileMbRef.getBuffer();
+ if (Error Err = CacheStream.commit())
+ return Err;
+ } else {
+ if (AddBuffer) {
+ AddBuffer(Job.Task, Job.ModuleID, std::move(ObjFileMbOrErr.get()));
+ } else {
+ auto StreamOrErr = AddStreamFunc(Job.Task, Job.ModuleID);
+ if (Error Err = StreamOrErr.takeError())
+ return Err;
+ auto &Stream = *StreamOrErr->get();
+ *Stream.OS << ObjFileMbRef.getBuffer();
+ if (Error Err = Stream.commit())
+ return Err;
+ }
}
}
- Base::cleanup();
+ return Error::success();
+}
+
+// Generates a JSON file describing the backend compilations, for the
+// distributor.
+Error lto::DistributionDriver::emitJson() {
+ using json::Array;
+ std::error_code EC;
+ raw_fd_ostream OS(DistributorJsonFile, EC);
+ if (EC)
+ return createStringError(EC, "Error while creating Json file");
+
+ json::OStream JOS(OS);
+ JOS.object([&]() {
+ // Information common to all jobs.
+ JOS.attributeObject("common", [&]() {
+ JOS.attribute("linker_output", Params.LinkerOutputFile);
+
+ JOS.attributeArray("args", [&]() {
+ JOS.value(Params.RemoteCompiler);
+
+ // Forward any supplied prepend options.
+ if (!Params.RemoteCompilerPrependArgs.empty())
+ for (auto &A : Params.RemoteCompilerPrependArgs)
+ JOS.value(A);
+
+ JOS.value("-c");
+
+ JOS.value(std::string("--target=") + Params.TargetTriple.str());
+
+ for (const auto &A : Params.CodegenOptions)
+ JOS.value(A);
+ });
+
+ JOS.attribute("inputs", Array(Params.CommonInputs));
+ });
+
+ // Per-compilation-job information.
+ JOS.attributeArray("jobs", [&]() {
+ for (const auto &J : Jobs) {
+ assert(J.Task != 0);
+ if (J.Cached) {
+ continue;
+ }
+
+ SmallVector<StringRef, 2> Inputs;
+ SmallVector<StringRef, 1> Outputs;
+
+ JOS.object([&]() {
+ JOS.attributeArray("args", [&]() {
+ JOS.value(J.ModuleID);
+ Inputs.push_back(J.ModuleID);
+
+ JOS.value(
+ std::string("-fthinlto-index=" + J.SummaryIndexPath.str()));
+ Inputs.push_back(J.SummaryIndexPath);
+
+ JOS.value("-o");
+ JOS.value(J.NativeObjectPath);
+ Outputs.push_back(J.NativeObjectPath);
+ });
+
+ // Add the bitcode files from which imports will be made. These do
+ // not explicitly appear on the backend compilation command lines
+ // but are recorded in the summary index shards.
+ append_range(Inputs, J.ImportsFiles);
+ JOS.attribute("inputs", Array(Inputs));
+
+ JOS.attribute("outputs", Array(Outputs));
+ });
+ }
+ });
+ });
+
+ return Error::success();
+}
+
+// Saves JSON file on a filesystem.
+Error lto::DistributionDriver::saveJson() {
+ DistributorJsonFile = sys::path::parent_path(Params.LinkerOutputFile);
+ TimeTraceScope TimeScope("Emit DTLTO JSON");
+ sys::path::append(DistributorJsonFile,
+ sys::path::stem(Params.LinkerOutputFile) + "." +
+ itostr(sys::Process::getProcessId()) +
+ ".dist-file.json");
+ if (Error E = emitJson())
+ return make_error<StringError>(
+ BCError + "failed to generate distributor JSON script: " +
+ DistributorJsonFile,
+ errorToErrorCode(std::move(E)));
+
+ // Add JSON file to the cleanup files list.
+ if (!SaveTemps)
+ AddToCleanup(DistributorJsonFile);
+ return Error::success();
+}
+
+// Invokes the distributor to compile uncached ThinLTO modules remotely.
+Error lto::DistributionDriver::operator()() {
+ if (Error E = saveJson())
+ return E;
+
+ TimeTraceScope TimeScope("Execute DTLTO distributor", Params.DistributorPath);
+ SmallVector<StringRef, 3> Args = {Params.DistributorPath};
+ append_range(Args, Params.DistributorArgs);
+ Args.push_back(DistributorJsonFile);
+ std::string ErrMsg;
+ if (sys::ExecuteAndWait(Args[0], Args,
+ /*Env=*/std::nullopt, /*Redirects=*/{},
+ /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg)) {
+ return make_error<StringError>(
+ BCError + "distributor execution failed" +
+ (!ErrMsg.empty() ? ": " + ErrMsg + Twine(".") : Twine(".")),
+ inconvertibleErrorCode());
+ }
+ return Error::success();
}
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 95faf3484c456..1e444ca92c443 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -1299,10 +1299,6 @@ Error LTO::checkPartiallySplit() {
}
Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
- llvm::scope_exit CleanUp([this]() { cleanup(); });
-
- if (Error EC = serializeInputsForDistribution())
- return EC;
// Compute "dead" symbols, we don't want to import/export these!
DenseSet<GlobalValue::GUID> GUIDPreservedSymbols;
@@ -1518,17 +1514,22 @@ SmallVector<StringRef> LTO::getLibFuncSymbols(const Triple &TT,
}
Error ThinBackendProc::emitFiles(
- const FunctionImporter::ImportMapTy &ImportList, llvm::StringRef ModulePath,
+ const FunctionImporter::ImportMapTy &ImportList, unsigned Task, llvm::StringRef ModulePath,
const std::string &NewModulePath) const {
- return emitFiles(ImportList, ModulePath, NewModulePath,
+ return emitFiles(ImportList, Task, ModulePath, NewModulePath,
NewModulePath + ".thinlto.bc",
- /*ImportsFiles=*/std::nullopt);
+ /*ImportsFiles=*/std::nullopt, nullptr, nullptr, nullptr,
+ nullptr);
}
Error ThinBackendProc::emitFiles(
- const FunctionImporter::ImportMapTy &ImportList, llvm::StringRef ModulePath,
+ const FunctionImporter::ImportMapTy &ImportList, unsigned Task, llvm::StringRef ModulePath,
const std::string &NewModulePath, StringRef SummaryPath,
- std::optional<std::reference_wrapper<ImportsFilesContainer>> ImportsFiles)
+ std::optional<std::reference_wrapper<ImportsFilesContainer>> ImportsFiles,
+ const FunctionImporter::ExportSetTy *ExportList,
+ const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes> *ResolvedODR,
+ const DenseSet<GlobalValue::GUID> *CfiFunctionDefs,
+ const DenseSet<GlobalValue::GUID> *CfiFunctionDecls)
const {
ModuleToSummariesForIndexTy ModuleToSummariesForIndex;
GVSummaryPtrSet DeclarationSummaries;
@@ -1538,18 +1539,32 @@ Error ThinBackendProc::emitFiles(
ImportList, ModuleToSummariesForIndex,
DeclarationSummaries);
- raw_fd_ostream OS(SummaryPath, EC, sys::fs::OpenFlags::OF_None);
- if (EC)
- return createFileError("cannot open " + Twine(SummaryPath), EC);
+ if (Conf.OnSummaryIndexStoreCb) {
+ std::unique_ptr<raw_pwrite_stream> PS = Conf.OnSummaryIndexStoreCb(Task);
+ writeIndexToFile(CombinedIndex, *PS, &ModuleToSummariesForIndex,
+ &DeclarationSummaries);
+ } else {
+ raw_fd_ostream OS(SummaryPath, EC, sys::fs::OpenFlags::OF_None);
+ if (EC)
+ return createFileError("cannot open " + Twine(SummaryPath), EC);
- writeIndexToFile(CombinedIndex, OS, &ModuleToSummariesForIndex,
- &DeclarationSummaries);
+ writeIndexToFile(CombinedIndex, OS, &ModuleToSummariesForIndex,
+ &DeclarationSummaries);
+ }
- if (ShouldEmitImportsFiles) {
- Error ImportsFilesError = EmitImportsFiles(
- ModulePath, NewModulePath + ".imports", ModuleToSummariesForIndex);
- if (ImportsFilesError)
- return ImportsFilesError;
+ if (Conf.OnImportsListStoreCb) {
+ std::vector<std::string> &ImportsListRef = Conf.OnImportsListStoreCb(Task);
+ for (const auto &ILI : ModuleToSummariesForIndex) {
+ if (ILI.first != ModulePath)
+ ImportsListRef.push_back(ILI.first);
+ }
+ } else {
+ if (ShouldEmitImportsFiles) {
+ Error ImportsFilesError = EmitImportsFiles(
+ ModulePath, NewModulePath + ".imports", ModuleToSummariesForIndex);
+ if (ImportsFilesError)
+ return ImportsFilesError;
+ }
}
// Optionally, store the imports files.
@@ -1558,6 +1573,19 @@ Error ThinBackendProc::emitFiles(
ModulePath, ModuleToSummariesForIndex,
[&](StringRef M) { ImportsFiles->get().push_back(M.str()); });
+ // Optionally, compute and store the LTO cache key for DTLTO.
+ if (Conf.OnCacheKeyStoreCb && ExportList && ResolvedODR && CfiFunctionDefs &&
+ CfiFunctionDecls) {
+ assert(ModuleToDefinedGVSummaries.count(ModulePath));
+ const GVSummaryMapTy &DefinedGlobals =
+ ModuleToDefinedGVSummaries.find(ModulePath)->second;
+
+ std::string &CacheKeyRef = Conf.OnCacheKeyStoreCb(Task);
+ CacheKeyRef = computeLTOCacheKey(
+ Conf, CombinedIndex, ModulePath, ImportList, *ExportList, *ResolvedODR,
+ DefinedGlobals, *CfiFunctionDefs, *CfiFunctionDecls);
+ }
+
return Error::success();
}
@@ -1632,7 +1660,7 @@ class InProcessThinBackend : public CGThinBackend {
Conf.CodeGenOnly, BitcodeLibFuncs);
};
if (ShouldEmitIndexFiles) {
- if (auto E = emitFiles(ImportList, ModuleID, ModuleID.str()))
+ if (auto E = emitFiles(ImportList, Task, ModuleID, ModuleID.str()))
return E;
}
@@ -1750,7 +1778,7 @@ class FirstRoundThinBackend : public InProcessThinBackend {
// FirstRoundThinBackend. However, these files are not generated for
// SecondRoundThinBackend.
if (ShouldEmitIndexFiles) {
- if (auto E = emitFiles(ImportList, ModuleID, ModuleID.str()))
+ if (auto E = emitFiles(ImportList, Task, ModuleID, ModuleID.str()))
return E;
}
@@ -1927,6 +1955,8 @@ namespace {
class WriteIndexesThinBackend : public ThinBackendProc {
std::string OldPrefix, NewPrefix, NativeObjectPrefix;
raw_fd_ostream *LinkedObjectsFile;
+ DenseSet<GlobalValue::GUID> CfiFunctionDefs;
+ DenseSet<GlobalValue::GUID> CfiFunctionDecls;
public:
WriteIndexesThinBackend(
@@ -1940,7 +1970,12 @@ class WriteIndexesThinBackend : public ThinBackendProc {
OnWrite, ShouldEmitImportsFiles, ThinLTOParallelism),
OldPrefix(OldPrefix), NewPrefix(NewPrefix),
NativeObjectPrefix(NativeObjectPrefix),
- LinkedObjectsFile(LinkedObjectsFile) {}
+ LinkedObjectsFile(LinkedObjectsFile) {
+ auto &Defs = CombinedIndex.cfiFunctionDefs();
+ CfiFunctionDefs.insert(Defs.guid_begin(), Defs.guid_end());
+ auto &Decls = CombinedIndex.cfiFunctionDecls();
+ CfiFunctionDecls.insert(Decls.guid_begin(), Decls.guid_end());
+ }
Error start(
unsigned Task, BitcodeModule BM,
@@ -1963,22 +1998,28 @@ class WriteIndexesThinBackend : public ThinBackendProc {
}
BackendThreadPool.async(
- [this](const StringRef ModulePath,
+ [this](unsigned Task, const StringRef ModulePath,
const FunctionImporter::ImportMapTy &ImportList,
+ const FunctionImporter::ExportSetTy &ExportList,
+ const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes>
+ &ResolvedODR,
const std::string &OldPrefix, const std::string &NewPrefix) {
std::string NewModulePath =
getThinLTOOutputFile(ModulePath, OldPrefix, NewPrefix);
- auto E = emitFiles(ImportList, ModulePath, NewModulePath);
+ auto E = emitFiles(ImportList, Task, ModulePath, NewModulePath,
+ NewModulePath + ".thinlto.bc",
+ /*ImportsFiles=*/std::nullopt, &ExportList,
+ &ResolvedODR, &CfiFunctionDefs, &CfiFunctionDecls);
if (E) {
std::unique_lock<std::mutex> L(ErrMu);
if (Err)
Err = joinErrors(std::move(*Err), std::move(E));
else
Err = std::move(E);
- return;
}
},
- ModulePath, ImportList, OldPrefix, NewPrefix);
+ Task, ModulePath, ImportList, ExportList, ResolvedODR, OldPrefix,
+ NewPrefix);
if (OnWrite)
OnWrite(std::string(ModulePath));
@@ -2352,462 +2393,3 @@ std::vector<int> lto::generateModulesOrdering(ArrayRef<BitcodeModule *> R) {
});
return ModulesOrdering;
}
-
-namespace {
-/// This out-of-process backend does not perform code generation when invoked
-/// for each task. Instead, it generates the necessary information (e.g., the
-/// summary index shard, import list, etc.) to enable code generation to be
-/// performed externally, similar to WriteIndexesThinBackend. The backend's
-/// `wait` function then invokes an external distributor process to carry out
-/// the backend compilations.
-class OutOfProcessThinBackend : public CGThinBackend {
- using SString = SmallString<128>;
-
- BumpPtrAllocator Alloc;
- StringSaver Saver{Alloc};
-
- SString LinkerOutputFile;
-
- SString DistributorPath;
- ArrayRef<StringRef> DistributorArgs;
-
- SString RemoteCompiler;
- ArrayRef<StringRef> RemoteCompilerPrependArgs;
- ArrayRef<StringRef> RemoteCompilerArgs;
-
- bool SaveTemps;
-
- SmallVector<StringRef, 0> CodegenOptions;
- DenseSet<StringRef> CommonInputs;
- // Number of the object files that have been already cached.
- std::atomic<size_t> CachedJobs{0};
- // Information specific to individual backend compilation job.
- struct Job {
- unsigned Task;
- StringRef ModuleID;
- StringRef NativeObjectPath;
- StringRef SummaryIndexPath;
- ImportsFilesContainer ImportsFiles;
- std::string CacheKey;
- AddStreamFn CacheAddStream;
- bool Cached = false;
- };
- // The set of backend compilations jobs.
- SmallVector<Job> Jobs;
-
- // A unique string to identify the current link.
- SmallString<8> UID;
-
- // The offset to the first ThinLTO task.
- unsigned ThinLTOTaskOffset;
-
- // The target triple to supply for backend compilations.
- llvm::Triple Triple;
-
- // Cache
- FileCache Cache;
-
- // Callback to add a pre-existing native object buffer to the link.
- AddBufferFn AddBuffer;
-
-public:
- OutOfProcessThinBackend(
- const Config &Conf, ModuleSummaryIndex &CombinedIndex,
- ThreadPoolStrategy ThinLTOParallelism,
- const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
- FileCache CacheFn, lto::IndexWriteCallback OnWrite,
- bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles,
- StringRef LinkerOutputFile, StringRef Distributor,
- ArrayRef<StringRef> DistributorArgs, StringRef RemoteCompiler,
- ArrayRef<StringRef> RemoteCompilerPrependArgs,
- ArrayRef<StringRef> RemoteCompilerArgs, bool SaveTemps,
- AddBufferFn AddBuffer)
- : CGThinBackend(Conf, CombinedIndex, ModuleToDefinedGVSummaries, OnWrite,
- ShouldEmitIndexFiles, ShouldEmitImportsFiles,
- ThinLTOParallelism),
- LinkerOutputFile(LinkerOutputFile), DistributorPath(Distributor),
- DistributorArgs(DistributorArgs), RemoteCompiler(RemoteCompiler),
- RemoteCompilerPrependArgs(RemoteCompilerPrependArgs),
- RemoteCompilerArgs(RemoteCompilerArgs), SaveTemps(SaveTemps),
- Cache(std::move(CacheFn)), AddBuffer(std::move(AddBuffer)) {}
-
- void setup(unsigned ThinLTONumTasks, unsigned ThinLTOTaskOffset,
- llvm::Triple Triple) override {
- UID = itostr(sys::Process::getProcessId());
- Jobs.resize((size_t)ThinLTONumTasks);
- this->ThinLTOTaskOffset = ThinLTOTaskOffset;
- this->Triple = std::move(Triple);
- this->Conf.Dtlto = 1;
- }
-
- virtual Error runThinLTOBackendThread(
- Job &J, const FunctionImporter::ImportMapTy &ImportList,
- const FunctionImporter::ExportSetTy &ExportList,
- const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes>
- &ResolvedODR) {
- {
- TimeTraceScope TimeScope("Emit individual index for DTLTO",
- J.SummaryIndexPath);
- if (auto E = emitFiles(ImportList, J.ModuleID, J.ModuleID.str(),
- J.SummaryIndexPath, J.ImportsFiles))
- return E;
- }
-
- if (!Cache.isValid() || !CombinedIndex.modulePaths().count(J.ModuleID) ||
- all_of(CombinedIndex.getModuleHash(J.ModuleID),
- [](uint32_t V) { return V == 0; }))
- // Cache disabled or no entry for this module in the combined index or
- // no module hash.
- return Error::success();
-
- TimeTraceScope TimeScope("Check cache for DTLTO", J.SummaryIndexPath);
- const GVSummaryMapTy &DefinedGlobals =
- ModuleToDefinedGVSummaries.find(J.ModuleID)->second;
-
- // The module may be cached, this helps handling it.
- J.CacheKey = computeLTOCacheKey(Conf, CombinedIndex, J.ModuleID, ImportList,
- ExportList, ResolvedODR, DefinedGlobals,
- CfiFunctionDefs, CfiFunctionDecls);
-
- // The module may be cached, this helps handling it.
- auto CacheAddStreamExp = Cache(J.Task, J.CacheKey, J.ModuleID);
- if (Error Err = CacheAddStreamExp.takeError())
- return Err;
- AddStreamFn &CacheAddStream = *CacheAddStreamExp;
- // If CacheAddStream is null, we have a cache hit and at this point
- // object file is already passed back to the linker.
- if (!CacheAddStream) {
- J.Cached = true; // Cache hit, mark the job as cached.
- CachedJobs.fetch_add(1);
- } else {
- // If CacheAddStream is not null, we have a cache miss and we need to
- // run the backend for codegen. Save cache 'add stream'
- // function for a later use.
- J.CacheAddStream = std::move(CacheAddStream);
- }
- return Error::success();
- }
-
- Error start(
- unsigned Task, BitcodeModule BM,
- const FunctionImporter::ImportMapTy &ImportList,
- const FunctionImporter::ExportSetTy &ExportList,
- const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes> &ResolvedODR,
- MapVector<StringRef, BitcodeModule> &ModuleMap) override {
-
- StringRef ModulePath = BM.getModuleIdentifier();
-
- SString ObjFilePath = sys::path::parent_path(LinkerOutputFile);
- sys::path::append(ObjFilePath, sys::path::stem(ModulePath) + "." +
- itostr(Task) + "." + UID + ".native.o");
-
- Job &J = Jobs[Task - ThinLTOTaskOffset];
- J = {Task,
- ModulePath,
- Saver.save(ObjFilePath.str()),
- Saver.save(ObjFilePath.str() + ".thinlto.bc"),
- {}, // Filled in by emitFiles below.
- "", /*CacheKey=*/
- nullptr,
- false};
-
- // Cleanup per-job temporary files on abnormal process exit.
- if (!SaveTemps) {
- llvm::sys::RemoveFileOnSignal(J.NativeObjectPath);
- if (!ShouldEmitIndexFiles)
- llvm::sys::RemoveFileOnSignal(J.SummaryIndexPath);
- }
-
- assert(ModuleToDefinedGVSummaries.count(ModulePath));
-
- // The BackendThreadPool is only used here to write the sharded index files
- // (similar to WriteIndexesThinBackend).
- BackendThreadPool.async(
- [=](Job &J, const FunctionImporter::ImportMapTy &ImportList,
- const FunctionImporter::ExportSetTy &ExportList,
- const std::map<GlobalValue::GUID, GlobalValue::LinkageTypes>
- &ResolvedODR) {
- if (LLVM_ENABLE_THREADS && Conf.TimeTraceEnabled)
- timeTraceProfilerInitialize(
- Conf.TimeTraceGranularity,
- "Emit individual index and check cache for DTLTO");
- Error E =
- runThinLTOBackendThread(J, ImportList, ExportList, ResolvedODR);
- if (E) {
- std::unique_lock<std::mutex> L(ErrMu);
- if (Err)
- Err = joinErrors(std::move(*Err), std::move(E));
- else
- Err = std::move(E);
- }
- if (LLVM_ENABLE_THREADS && Conf.TimeTraceEnabled)
- timeTraceProfilerFinishThread();
- },
- std::ref(J), std::ref(ImportList), std::ref(ExportList),
- std::ref(ResolvedODR));
-
- return Error::success();
- }
-
- // Derive a set of Clang options that will be shared/common for all DTLTO
- // backend compilations. We are intentionally minimal here as these options
- // must remain synchronized with the behavior of Clang. DTLTO does not support
- // all the features available with in-process LTO. More features are expected
- // to be added over time. Users can specify Clang options directly if a
- // feature is not supported. Note that explicitly specified options that imply
- // additional input or output file dependencies must be communicated to the
- // distribution system, potentially by setting extra options on the
- // distributor program.
- void buildCommonRemoteCompilerOptions() {
- const lto::Config &C = Conf;
- auto &Ops = CodegenOptions;
-
- Ops.push_back(Saver.save("-O" + Twine(C.OptLevel)));
-
- if (C.Options.EmitAddrsig)
- Ops.push_back("-faddrsig");
- if (C.Options.FunctionSections)
- Ops.push_back("-ffunction-sections");
- if (C.Options.DataSections)
- Ops.push_back("-fdata-sections");
-
- if (C.RelocModel == Reloc::PIC_)
- // Clang doesn't have -fpic for all triples.
- if (!Triple.isOSBinFormatCOFF())
- Ops.push_back("-fpic");
-
- // Turn on/off warnings about profile cfg mismatch (default on)
- // --lto-pgo-warn-mismatch.
- if (!C.PGOWarnMismatch) {
- Ops.push_back("-mllvm");
- Ops.push_back("-no-pgo-warn-mismatch");
- }
-
- // Enable sample-based profile guided optimizations.
- // Sample profile file path --lto-sample-profile=<value>.
- if (!C.SampleProfile.empty()) {
- Ops.push_back(
- Saver.save("-fprofile-sample-use=" + Twine(C.SampleProfile)));
- CommonInputs.insert(C.SampleProfile);
- }
-
- // We don't know which of options will be used by Clang.
- Ops.push_back("-Wno-unused-command-line-argument");
-
- // Forward any supplied options.
- if (!RemoteCompilerArgs.empty())
- for (auto &a : RemoteCompilerArgs)
- Ops.push_back(a);
- }
-
- // Generates a JSON file describing the backend compilations, for the
- // distributor.
- bool emitDistributorJson(StringRef DistributorJson) {
- using json::Array;
- std::error_code EC;
- raw_fd_ostream OS(DistributorJson, EC);
- if (EC)
- return false;
-
- json::OStream JOS(OS);
- JOS.object([&]() {
- // Information common to all jobs.
- JOS.attributeObject("common", [&]() {
- JOS.attribute("linker_output", LinkerOutputFile);
-
- JOS.attributeArray("args", [&]() {
- JOS.value(RemoteCompiler);
-
- // Forward any supplied prepend options.
- if (!RemoteCompilerPrependArgs.empty())
- for (auto &A : RemoteCompilerPrependArgs)
- JOS.value(A);
-
- JOS.value("-c");
-
- JOS.value(Saver.save("--target=" + Triple.str()));
-
- for (const auto &A : CodegenOptions)
- JOS.value(A);
- });
-
- JOS.attribute("inputs", Array(CommonInputs));
- });
-
- // Per-compilation-job information.
- JOS.attributeArray("jobs", [&]() {
- for (const auto &J : Jobs) {
- assert(J.Task != 0);
- if (J.Cached) {
- assert(!Cache.getCacheDirectoryPath().empty());
- continue;
- }
-
- SmallVector<StringRef, 2> Inputs;
- SmallVector<StringRef, 1> Outputs;
-
- JOS.object([&]() {
- JOS.attributeArray("args", [&]() {
- JOS.value(J.ModuleID);
- Inputs.push_back(J.ModuleID);
-
- JOS.value(
- Saver.save("-fthinlto-index=" + Twine(J.SummaryIndexPath)));
- Inputs.push_back(J.SummaryIndexPath);
-
- JOS.value("-o");
- JOS.value(J.NativeObjectPath);
- Outputs.push_back(J.NativeObjectPath);
- });
-
- // Add the bitcode files from which imports will be made. These do
- // not explicitly appear on the backend compilation command lines
- // but are recorded in the summary index shards.
- llvm::append_range(Inputs, J.ImportsFiles);
- JOS.attribute("inputs", Array(Inputs));
-
- JOS.attribute("outputs", Array(Outputs));
- });
- }
- });
- });
-
- return true;
- }
-
- void removeFile(StringRef FileName) {
- std::error_code EC = sys::fs::remove(FileName, true);
- if (EC && EC != std::make_error_code(std::errc::no_such_file_or_directory))
- errs() << "warning: could not remove the file '" << FileName
- << "': " << EC.message() << "\n";
- }
-
- Error wait() override {
- // Wait for the information on the required backend compilations to be
- // gathered.
- BackendThreadPool.wait();
- if (Err)
- return std::move(*Err);
-
- llvm::scope_exit CleanPerJobFiles([&] {
- llvm::TimeTraceScope TimeScope("Remove DTLTO temporary files");
- if (!SaveTemps)
- for (auto &Job : Jobs) {
- removeFile(Job.NativeObjectPath);
- if (!ShouldEmitIndexFiles)
- removeFile(Job.SummaryIndexPath);
- }
- });
-
- const StringRef BCError = "DTLTO backend compilation: ";
-
- buildCommonRemoteCompilerOptions();
-
- SString JsonFile = sys::path::parent_path(LinkerOutputFile);
- {
- llvm::TimeTraceScope TimeScope("Emit DTLTO JSON");
- sys::path::append(JsonFile, sys::path::stem(LinkerOutputFile) + "." +
- UID + ".dist-file.json");
- // Cleanup DTLTO JSON file on abnormal process exit.
- if (!SaveTemps)
- llvm::sys::RemoveFileOnSignal(JsonFile);
- if (!emitDistributorJson(JsonFile))
- return make_error<StringError>(
- BCError + "failed to generate distributor JSON script: " + JsonFile,
- inconvertibleErrorCode());
- }
- llvm::scope_exit CleanJson([&] {
- if (!SaveTemps)
- removeFile(JsonFile);
- });
-
- {
- llvm::TimeTraceScope TimeScope("Execute DTLTO distributor",
- DistributorPath);
- // Checks if we have any jobs that don't have corresponding cache entries.
- if (CachedJobs.load() < Jobs.size()) {
- SmallVector<StringRef, 3> Args = {DistributorPath};
- llvm::append_range(Args, DistributorArgs);
- Args.push_back(JsonFile);
- std::string ErrMsg;
- if (sys::ExecuteAndWait(Args[0], Args,
- /*Env=*/std::nullopt, /*Redirects=*/{},
- /*SecondsToWait=*/0, /*MemoryLimit=*/0,
- &ErrMsg)) {
- return make_error<StringError>(
- BCError + "distributor execution failed" +
- (!ErrMsg.empty() ? ": " + ErrMsg + Twine(".") : Twine(".")),
- inconvertibleErrorCode());
- }
- }
- }
-
- {
- llvm::TimeTraceScope FilesScope("Add DTLTO files to the link");
- for (auto &Job : Jobs) {
- if (!Job.CacheKey.empty() && Job.Cached) {
- assert(Cache.isValid());
- continue;
- }
- // Load the native object from a file into a memory buffer
- // and store its contents in the output buffer.
- auto ObjFileMbOrErr =
- MemoryBuffer::getFile(Job.NativeObjectPath, /*IsText=*/false,
- /*RequiresNullTerminator=*/false);
- if (std::error_code EC = ObjFileMbOrErr.getError())
- return make_error<StringError>(
- BCError + "cannot open native object file: " +
- Job.NativeObjectPath + ": " + EC.message(),
- inconvertibleErrorCode());
-
- if (Cache.isValid()) {
- // Cache hits are taken care of earlier. At this point, we could only
- // have cache misses.
- assert(Job.CacheAddStream);
- MemoryBufferRef ObjFileMbRef =
- ObjFileMbOrErr->get()->getMemBufferRef();
- // Obtain a file stream for a storing a cache entry.
- auto CachedFileStreamOrErr =
- Job.CacheAddStream(Job.Task, Job.ModuleID);
- if (!CachedFileStreamOrErr)
- return joinErrors(
- CachedFileStreamOrErr.takeError(),
- createStringError(inconvertibleErrorCode(),
- "Cannot get a cache file stream: %s",
- Job.NativeObjectPath.data()));
- // Store a file buffer into the cache stream.
- auto &CacheStream = *(CachedFileStreamOrErr->get());
- *(CacheStream.OS) << ObjFileMbRef.getBuffer();
- if (Error Err = CacheStream.commit())
- return Err;
- } else {
- AddBuffer(Job.Task, Job.ModuleID, std::move(*ObjFileMbOrErr));
- }
- }
- }
- return Error::success();
- }
-};
-} // end anonymous namespace
-
-ThinBackend lto::createOutOfProcessThinBackend(
- ThreadPoolStrategy Parallelism, lto::IndexWriteCallback OnWrite,
- bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles,
- StringRef LinkerOutputFile, StringRef Distributor,
- ArrayRef<StringRef> DistributorArgs, StringRef RemoteCompiler,
- ArrayRef<StringRef> RemoteCompilerPrependArgs,
- ArrayRef<StringRef> RemoteCompilerArgs, bool SaveTemps,
- AddBufferFn AddBuffer) {
- auto Func =
- [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex,
- const DenseMap<StringRef, GVSummaryMapTy> &ModuleToDefinedGVSummaries,
- AddStreamFn, FileCache Cache, ArrayRef<StringRef> BitcodeLibFuncs) {
- return std::make_unique<OutOfProcessThinBackend>(
- Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries, Cache,
- OnWrite, ShouldEmitIndexFiles, ShouldEmitImportsFiles,
- LinkerOutputFile, Distributor, DistributorArgs, RemoteCompiler,
- RemoteCompilerPrependArgs, RemoteCompilerArgs, SaveTemps,
- AddBuffer);
- };
- return ThinBackend(Func, Parallelism);
-}
diff --git a/llvm/test/ThinLTO/X86/dtlto/timetrace.ll b/llvm/test/ThinLTO/X86/dtlto/timetrace.ll
index cab9fa04a3bb9..73b70188221a1 100644
--- a/llvm/test/ThinLTO/X86/dtlto/timetrace.ll
+++ b/llvm/test/ThinLTO/X86/dtlto/timetrace.ll
@@ -24,6 +24,10 @@ RUN: %python filter_order_and_pprint.py %t.json | FileCheck %s
CHECK-NOT: "name"
CHECK: "name": "Add DTLTO files to the link"
CHECK-SAME: "pid": [[#PID:]],
+CHECK-NEXT: "name": "Add input for DTLTO"
+CHECK-SAME: "pid": [[#PID:]],
+CHECK-NEXT: "name": "Add input for DTLTO"
+CHECK-SAME: "pid": [[#PID:]],
CHECK-NEXT: "name": "Emit DTLTO JSON"
CHECK-NEXT: "name": "Emit individual index for DTLTO"
CHECK-SAME: t1.1.[[#PID]].native.o.thinlto.bc"
@@ -33,6 +37,8 @@ CHECK-NEXT: "name": "Execute DTLTO distributor", "{{.*}}"
CHECK-NEXT: "name": "Remove DTLTO temporary files"
CHECK-NEXT: "name": "Total Add DTLTO files to the link"
CHECK-SAME: "count": 1,
+CHECK-NEXT: "name": "Total Add input for DTLTO"
+CHECK-SAME: "count": 2,
CHECK-NEXT: "name": "Total Emit DTLTO JSON"
CHECK-SAME: "count": 1,
CHECK-NEXT: "name": "Total Emit individual index for DTLTO"
diff --git a/llvm/tools/llvm-lto2/CMakeLists.txt b/llvm/tools/llvm-lto2/CMakeLists.txt
index 60ddce1898a6b..4d0c93680d355 100644
--- a/llvm/tools/llvm-lto2/CMakeLists.txt
+++ b/llvm/tools/llvm-lto2/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
BitReader
CodeGen
Core
+ DTLTO
Linker
LTO
MC
diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp
index 839324e396850..a0fb1b9e99bd2 100644
--- a/llvm/tools/llvm-lto2/llvm-lto2.cpp
+++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp
@@ -19,6 +19,7 @@
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/CodeGen/CommandFlags.h"
#include "llvm/IR/DiagnosticPrinter.h"
+#include "llvm/DTLTO/DTLTO.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Plugins/PassPlugin.h"
#include "llvm/Remarks/HotnessThresholdParser.h"
@@ -443,7 +444,7 @@ static int run(int argc, char **argv) {
};
ThinBackend Backend;
- if (ThinLTODistributedIndexes)
+ if (ThinLTODistributedIndexes || !DTLTODistributor.empty())
Backend = createWriteIndexesThinBackend(llvm::hardware_concurrency(Threads),
/*OldPrefix=*/"",
/*NewPrefix=*/"",
@@ -451,13 +452,7 @@ static int run(int argc, char **argv) {
ThinLTOEmitImports,
/*LinkedObjectsFile=*/nullptr,
/*OnWrite=*/{});
- else if (!DTLTODistributor.empty()) {
- Backend = createOutOfProcessThinBackend(
- llvm::heavyweight_hardware_concurrency(Threads),
- /*OnWrite=*/{}, ThinLTOEmitIndexes, ThinLTOEmitImports, OutputFilename,
- DTLTODistributor, DTLTODistributorArgsSV, DTLTOCompiler,
- DTLTOCompilerPrependArgsSV, DTLTOCompilerArgsSV, SaveTemps, AddBuffer);
- } else
+ else
Backend = createInProcessThinBackend(
llvm::heavyweight_hardware_concurrency(Threads),
/* OnWrite */ {}, ThinLTOEmitIndexes, ThinLTOEmitImports);
@@ -480,7 +475,17 @@ static int run(int argc, char **argv) {
LTO::LTOKind LTOMode = UnifiedLTOMode;
- LTO Lto(std::move(Conf), std::move(Backend), 1, LTOMode);
+ std::unique_ptr<LTO> Lto;
+ if (!DTLTODistributor.empty()) {
+ Lto = std::make_unique<DTLTO>(
+ std::move(Conf), std::move(Backend), 1, LTOMode, nullptr,
+ ThinLTOEmitIndexes, ThinLTOEmitImports, OutputFilename,
+ DTLTODistributor, DTLTODistributorArgsSV, DTLTOCompiler,
+ DTLTOCompilerPrependArgsSV, DTLTOCompilerArgsSV, AddBuffer, SaveTemps);
+ } else {
+ Lto =
+ std::make_unique<LTO>(std::move(Conf), std::move(Backend), 1, LTOMode);
+ }
for (std::string F : InputFilenames) {
std::unique_ptr<MemoryBuffer> MB = check(MemoryBuffer::getFile(F), F);
@@ -514,7 +519,7 @@ static int run(int argc, char **argv) {
continue;
MBs.push_back(std::move(MB));
- check(Lto.add(std::move(Input), Res), F);
+ check(Lto->add(std::move(Input), Res), F);
}
if (!CommandLineResolutions.empty()) {
@@ -527,7 +532,7 @@ static int run(int argc, char **argv) {
if (HasErrors)
return 1;
- Lto.setBitcodeLibFuncs(
+ Lto->setBitcodeLibFuncs(
SmallVector<StringRef>(BitcodeLibFuncs.begin(), BitcodeLibFuncs.end()));
FileCache Cache;
@@ -535,7 +540,7 @@ static int run(int argc, char **argv) {
Cache = check(localCache("ThinLTO", "Thin", CacheDir, AddBuffer),
"failed to create cache");
- check(Lto.run(AddStream, Cache), "LTO::run failed");
+ check(Lto->run(AddStream, Cache), "LTO::run failed");
return static_cast<int>(HasErrors);
}
More information about the llvm-commits
mailing list