Skip to content

[DTLTO][LLD][ELF] Add support for Integrated Distributed ThinLTO #142757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions cross-project-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ set(CROSS_PROJECT_TEST_DEPS
FileCheck
check-gdb-llvm-support
count
llvm-dwarfdump
llvm-ar
llvm-config
llvm-dwarfdump
llvm-objdump
split-file
not
split-file
)

if ("clang" IN_LIST LLVM_ENABLE_PROJECTS)
Expand Down Expand Up @@ -94,6 +95,13 @@ add_lit_testsuite(check-cross-amdgpu "Running AMDGPU cross-project tests"
DEPENDS clang
)

# DTLTO tests.
add_lit_testsuite(check-cross-dtlto "Running DTLTO cross-project tests"
${CMAKE_CURRENT_BINARY_DIR}/dtlto
EXCLUDE_FROM_CHECK_ALL
DEPENDS ${CROSS_PROJECT_TEST_DEPS}
)

# Add check-cross-project-* targets.
add_lit_testsuites(CROSS_PROJECT ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${CROSS_PROJECT_TEST_DEPS}
Expand Down
3 changes: 3 additions & 0 deletions cross-project-tests/dtlto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Tests for DTLTO (Integrated Distributed ThinLTO) functionality.

These are integration tests as DTLTO invokes `clang` for code-generation.
65 changes: 65 additions & 0 deletions cross-project-tests/dtlto/archive-thin.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# REQUIRES: x86-registered-target,ld.lld,llvm-ar

# Test that a DTLTO link succeeds and outputs the expected set of files
# correctly when thin archives are present.

RUN: rm -rf %t && split-file %s %t && cd %t
RUN: %clang --target=x86_64-linux-gnu -flto=thin -c \
RUN: foo.c bar.c dog.c cat.c _start.c

RUN: llvm-ar rcs foo.a foo.o --thin
# Create this bitcode thin archive in a subdirectory to test the expansion of
# the path to a bitcode file that is referenced using "..", e.g., in this case
# "../bar.o".
RUN: mkdir lib
RUN: llvm-ar rcs lib/bar.a bar.o --thin
# Create this bitcode thin archive with an absolute path entry containing "..".
RUN: llvm-ar rcs dog.a %t/lib/../dog.o --thin
RUN: llvm-ar rcs cat.a cat.o --thin
RUN: llvm-ar rcs _start.a _start.o --thin

RUN: mkdir %t/out && cd %t/out

RUN: ld.lld %t/foo.a %t/lib/bar.a ../_start.a %t/cat.a \
RUN: --whole-archive ../dog.a \
RUN: --thinlto-distributor=%python \
RUN: --thinlto-distributor-arg=%llvm_src_root/utils/dtlto/local.py \
RUN: --thinlto-remote-compiler=%clang \
RUN: --save-temps

# Check that the required output files have been created.
RUN: ls | FileCheck %s --check-prefix=OUTPUTS \
RUN: --implicit-check-not=cat --implicit-check-not=foo

# JSON jobs description.
OUTPUTS-DAG: a.[[PID:[a-zA-Z0-9_]+]].dist-file.json

# Individual summary index files.
OUTPUTS-DAG: dog.1.[[PID]].native.o.thinlto.bc{{$}}
OUTPUTS-DAG: _start.2.[[PID]].native.o.thinlto.bc{{$}}
OUTPUTS-DAG: bar.3.[[PID]].native.o.thinlto.bc{{$}}

# Native output object files.
OUTPUTS-DAG: dog.1.[[PID]].native.o{{$}}
OUTPUTS-DAG: _start.2.[[PID]].native.o{{$}}
OUTPUTS-DAG: bar.3.[[PID]].native.o{{$}}

#--- foo.c
__attribute__((retain)) void foo() {}

#--- bar.c
extern void foo();
__attribute__((retain)) void bar() { foo(); }

#--- dog.c
__attribute__((retain)) void dog() {}

#--- cat.c
__attribute__((retain)) void cat() {}

#--- _start.c
extern void bar();
__attribute__((retain)) void _start() {
bar();
}

35 changes: 35 additions & 0 deletions cross-project-tests/dtlto/dtlto.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// REQUIRES: x86-registered-target,ld.lld

/// Simple test that DTLTO works with a single input bitcode file and that
/// --save-temps can be applied to the remote compilation.
// RUN: rm -rf %t && mkdir %t && cd %t

// RUN: %clang --target=x86_64-linux-gnu -c -flto=thin %s

// RUN: ld.lld dtlto.o \
// RUN: --thinlto-distributor=%python \
// RUN: --thinlto-distributor-arg=%llvm_src_root/utils/dtlto/local.py \
// RUN: --thinlto-remote-compiler=%clang \
// RUN: --thinlto-remote-compiler-arg=--save-temps

/// Check that the required output files have been created.
// RUN: ls | count 10
// RUN: ls | FileCheck %s

/// Produced by the bitcode compilation.
// CHECK-DAG: {{^}}dtlto.o{{$}}

/// Linked ELF.
// CHECK-DAG: {{^}}a.out{{$}}

/// --save-temps output for the backend compilation.
// CHECK-DAG: {{^}}dtlto.s{{$}}
// CHECK-DAG: {{^}}dtlto.s.0.preopt.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.1.promote.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.2.internalize.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.3.import.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.4.opt.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.5.precodegen.bc{{$}}
// CHECK-DAG: {{^}}dtlto.s.resolution.txt{{$}}

int _start() { return 0; }
2 changes: 2 additions & 0 deletions cross-project-tests/dtlto/lit.local.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
if "clang" not in config.available_features:
config.unsupported = True
4 changes: 3 additions & 1 deletion cross-project-tests/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell)

# suffixes: A list of file extensions to treat as test files.
config.suffixes = [".c", ".cl", ".cpp", ".m"]
config.suffixes = [".c", ".cl", ".cpp", ".m", ".test"]

# excludes: A list of directories to exclude from the testsuite. The 'Inputs'
# subdirectories contain auxiliary inputs for various tests in their parent
Expand Down Expand Up @@ -107,6 +107,8 @@ def get_required_attr(config, attr_name):
if lldb_path is not None:
config.available_features.add("lldb")

if llvm_config.use_llvm_tool("llvm-ar"):
config.available_features.add("llvm-ar")

def configure_dexter_substitutions():
"""Configure substitutions for host platform and return list of dependencies"""
Expand Down
4 changes: 4 additions & 0 deletions lld/ELF/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ struct Config {
llvm::SmallVector<llvm::StringRef, 0> searchPaths;
llvm::SmallVector<llvm::StringRef, 0> symbolOrderingFile;
llvm::SmallVector<llvm::StringRef, 0> thinLTOModulesToCompile;
llvm::StringRef dtltoDistributor;
llvm::SmallVector<llvm::StringRef, 0> dtltoDistributorArgs;
llvm::StringRef dtltoCompiler;
llvm::SmallVector<llvm::StringRef, 0> dtltoCompilerArgs;
llvm::SmallVector<llvm::StringRef, 0> undefined;
llvm::SmallVector<SymbolVersion, 0> dynamicList;
llvm::SmallVector<uint8_t, 0> buildIdVector;
Expand Down
5 changes: 5 additions & 0 deletions lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,11 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
args.hasFlag(OPT_dependent_libraries, OPT_no_dependent_libraries, true);
ctx.arg.disableVerify = args.hasArg(OPT_disable_verify);
ctx.arg.discard = getDiscard(args);
ctx.arg.dtltoDistributor = args.getLastArgValue(OPT_thinlto_distributor_eq);
ctx.arg.dtltoDistributorArgs =
args::getStrings(args, OPT_thinlto_distributor_arg);
ctx.arg.dtltoCompiler = args.getLastArgValue(OPT_thinlto_compiler_eq);
ctx.arg.dtltoCompilerArgs = args::getStrings(args, OPT_thinlto_compiler_arg);
ctx.arg.dwoDir = args.getLastArgValue(OPT_plugin_opt_dwo_dir_eq);
ctx.arg.dynamicLinker = getDynamicLinker(ctx, args);
ctx.arg.ehFrameHdr =
Expand Down
39 changes: 35 additions & 4 deletions lld/ELF/InputFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/LTO/LTO.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/IRObjectFile.h"
#include "llvm/Support/ARMAttributeParser.h"
#include "llvm/Support/ARMBuildAttributes.h"
Expand Down Expand Up @@ -1739,6 +1740,33 @@ static uint8_t getOsAbi(const Triple &t) {
}
}

// For DTLTO, bitcode member names must be valid paths to files on disk.
// For thin archives, resolve `memberPath` relative to the archive's location.
// Returns true if adjusted; false otherwise. Non-thin archives are unsupported.
static bool dtltoAdjustMemberPathIfThinArchive(Ctx &ctx, StringRef archivePath,
std::string &memberPath) {
assert(!archivePath.empty() && !ctx.arg.dtltoDistributor.empty());

// Check if the archive file is a thin archive by reading its header.
auto bufferOrErr =
MemoryBuffer::getFileSlice(archivePath, sizeof(ThinArchiveMagic) - 1, 0);
if (std::error_code ec = bufferOrErr.getError()) {
ErrAlways(ctx) << "cannot open " << archivePath << ": " << ec.message();
return false;
}

if (!bufferOrErr->get()->getBuffer().starts_with(ThinArchiveMagic))
return false;

SmallString<64> resolvedPath;
if (path::is_relative(memberPath)) {
resolvedPath = path::parent_path(archivePath);
path::append(resolvedPath, memberPath);
memberPath = resolvedPath.str();
}
return true;
}

BitcodeFile::BitcodeFile(Ctx &ctx, MemoryBufferRef mb, StringRef archiveName,
uint64_t offsetInArchive, bool lazy)
: InputFile(ctx, BitcodeKind, mb) {
Expand All @@ -1756,10 +1784,13 @@ BitcodeFile::BitcodeFile(Ctx &ctx, MemoryBufferRef mb, StringRef archiveName,
// symbols later in the link stage). So we append file offset to make
// filename unique.
StringSaver &ss = ctx.saver;
StringRef name = archiveName.empty()
? ss.save(path)
: ss.save(archiveName + "(" + path::filename(path) +
" at " + utostr(offsetInArchive) + ")");
StringRef name =
(archiveName.empty() ||
(!ctx.arg.dtltoDistributor.empty() &&
dtltoAdjustMemberPathIfThinArchive(ctx, archiveName, path)))
? ss.save(path)
: ss.save(archiveName + "(" + path::filename(path) + " at " +
utostr(offsetInArchive) + ")");
MemoryBufferRef mbref(mb.getBuffer(), name);

obj = CHECK2(lto::InputFile::create(mbref), this);
Expand Down
7 changes: 7 additions & 0 deletions lld/ELF/LTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) {
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.dtltoCompilerArgs, !ctx.arg.saveTempsArgs.empty());
} else {
backend = lto::createInProcessThinBackend(
llvm::heavyweight_hardware_concurrency(ctx.arg.thinLTOJobs),
Expand Down
12 changes: 11 additions & 1 deletion lld/ELF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,17 @@ def thinlto_object_suffix_replace_eq: JJ<"thinlto-object-suffix-replace=">;
def thinlto_prefix_replace_eq: JJ<"thinlto-prefix-replace=">;
def thinlto_single_module_eq: JJ<"thinlto-single-module=">,
HelpText<"Specify a single module to compile in ThinLTO mode, for debugging only">;

def thinlto_distributor_eq: JJ<"thinlto-distributor=">,
HelpText<"Distributor to use for ThinLTO backend compilations. If specified, "
"ThinLTO backend compilations will be distributed.">;
defm thinlto_distributor_arg: EEq<"thinlto-distributor-arg", "Arguments to "
"pass to the ThinLTO distributor">;
def thinlto_compiler_eq: JJ<"thinlto-remote-compiler=">,
HelpText<"Compiler for the ThinLTO distributor to invoke for ThinLTO backend "
"compilations">;
defm thinlto_compiler_arg: EEq<"thinlto-remote-compiler-arg", "Compiler "
"arguments for the ThinLTO distributor to pass for ThinLTO backend "
"compilations">;
defm fat_lto_objects: BB<"fat-lto-objects",
"Use the .llvm.lto section, which contains LLVM bitcode, in fat LTO object files to perform LTO.",
"Ignore the .llvm.lto section in relocatable object files (default).">;
Expand Down
42 changes: 42 additions & 0 deletions lld/docs/DTLTO.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Integrated Distributed ThinLTO (DTLTO)
======================================

Integrated Distributed ThinLTO (DTLTO) enables the distribution of backend
ThinLTO compilations via external distribution systems, such as Incredibuild,
during the traditional link step.

The implementation is documented here: https://llvm.org/docs/DTLTO.html.

Currently, DTLTO is only supported in ELF LLD. Support will be added to other
LLD flavours in the future.

ELF LLD
-------

The command-line interface is as follows:

- ``--thinlto-distributor=<path>``
Specifies the file to execute as the distributor process. If specified,
ThinLTO backend compilations will be distributed.

- ``--thinlto-remote-compiler=<path>``
Specifies the path to the compiler that the distributor process will use for
backend compilations. The compiler invoked must match the version of LLD.

- ``--thinlto-distributor-arg=<arg>``
Specifies ``<arg>`` on the command line when invoking the distributor.
Can be specified multiple times.

- ``--thinlto-remote-compiler-arg=<arg>``
Appends ``<arg>`` to the remote compiler's command line.
Can be specified multiple times.

Options that introduce extra input/output files may cause miscompilation if
the distribution system does not automatically handle pushing/fetching them to
remote nodes. In such cases, configure the distributor - possibly using
``--thinlto-distributor-arg=`` - to manage these dependencies. See the
distributor documentation for details.

Some LLD LTO options (e.g., ``--lto-sample-profile=<file>``) are supported.
Currently, other options are silently accepted but do not have the intended
effect. Support for such options will be expanded in the future.
1 change: 1 addition & 0 deletions lld/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,4 @@ document soon.
ELF/start-stop-gc
ELF/warn_backrefs
MachO/index
DTLTO
Loading
Loading