Skip to content

Commit aa0883b

Browse files
committed
[lld/mac] Add support for distributed ThinLTO
Adds support for the following flags: * --thinlto-index-only, --thinlto-index-only= * --thinlto-emit-imports-files * --thinlto-emit-index-files * --thinlto-object-suffix-replace= * --thinlto-prefix-replace= See https://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html for some words on --thinlto-index-only. I don't really need the other flags, but they were in the vicinity and _someone_ might need them, so I figured I'd add them too. `-object_path_lto` now sets `c.AlwaysEmitRegularLTOObj` as in the other ports, which means it can now only point to a filename for non-thin LTO. I think that was the intent of D129705 anyways, so update test/MachO/lto-object-path.ll to use a non-thin bitcode file for that test. Differential Revision: https://reviews.llvm.org/D138451
1 parent ee73d24 commit aa0883b

19 files changed

+668
-21
lines changed

lld/ELF/InputFiles.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,9 +1773,7 @@ bool InputFile::shouldExtractForCommon(StringRef name) {
17731773
}
17741774

17751775
std::string elf::replaceThinLTOSuffix(StringRef path) {
1776-
StringRef suffix = config->thinLTOObjectSuffixReplace.first;
1777-
StringRef repl = config->thinLTOObjectSuffixReplace.second;
1778-
1776+
auto [suffix, repl] = config->thinLTOObjectSuffixReplace;
17791777
if (path.consume_back(suffix))
17801778
return (path + repl).str();
17811779
return std::string(path);

lld/MachO/Config.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ struct Configuration {
133133
bool emitEncryptionInfo = false;
134134
bool emitInitOffsets = false;
135135
bool emitChainedFixups = false;
136+
bool thinLTOEmitImportsFiles;
137+
bool thinLTOEmitIndexFiles;
138+
bool thinLTOIndexOnly;
136139
bool timeTraceEnabled = false;
137140
bool dataConst = false;
138141
bool dedupStrings = true;
@@ -164,6 +167,9 @@ struct Configuration {
164167
uint32_t ltoo = 2;
165168
llvm::CachePruningPolicy thinLTOCachePolicy;
166169
llvm::StringRef thinLTOCacheDir;
170+
llvm::StringRef thinLTOIndexOnlyArg;
171+
std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
172+
std::pair<llvm::StringRef, llvm::StringRef> thinLTOPrefixReplace;
167173
bool deadStripDylibs = false;
168174
bool demangle = false;
169175
bool deadStrip = false;

lld/MachO/Driver.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,20 @@ static const char *getReproduceOption(InputArgList &args) {
858858
return getenv("LLD_REPRODUCE");
859859
}
860860

861+
// Parse options of the form "old;new".
862+
static std::pair<StringRef, StringRef> getOldNewOptions(opt::InputArgList &args,
863+
unsigned id) {
864+
auto *arg = args.getLastArg(id);
865+
if (!arg)
866+
return {"", ""};
867+
868+
StringRef s = arg->getValue();
869+
std::pair<StringRef, StringRef> ret = s.split(';');
870+
if (ret.second.empty())
871+
error(arg->getSpelling() + " expects 'old;new' format, but got " + s);
872+
return ret;
873+
}
874+
861875
static void parseClangOption(StringRef opt, const Twine &msg) {
862876
std::string err;
863877
raw_string_ostream os(err);
@@ -1549,6 +1563,25 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
15491563
error("--lto-O: invalid optimization level: " + Twine(config->ltoo));
15501564
config->thinLTOCacheDir = args.getLastArgValue(OPT_cache_path_lto);
15511565
config->thinLTOCachePolicy = getLTOCachePolicy(args);
1566+
config->thinLTOEmitImportsFiles = args.hasArg(OPT_thinlto_emit_imports_files);
1567+
config->thinLTOEmitIndexFiles = args.hasArg(OPT_thinlto_emit_index_files) ||
1568+
args.hasArg(OPT_thinlto_index_only) ||
1569+
args.hasArg(OPT_thinlto_index_only_eq);
1570+
config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) ||
1571+
args.hasArg(OPT_thinlto_index_only_eq);
1572+
config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq);
1573+
config->thinLTOObjectSuffixReplace =
1574+
getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq);
1575+
config->thinLTOPrefixReplace =
1576+
getOldNewOptions(args, OPT_thinlto_prefix_replace_eq);
1577+
if (config->thinLTOEmitIndexFiles && !config->thinLTOIndexOnly) {
1578+
if (args.hasArg(OPT_thinlto_object_suffix_replace_eq))
1579+
error("--thinlto-object-suffix-replace is not supported with "
1580+
"--thinlto-emit-index-files");
1581+
else if (args.hasArg(OPT_thinlto_prefix_replace_eq))
1582+
error("--thinlto-prefix-replace is not supported with "
1583+
"--thinlto-emit-index-files");
1584+
}
15521585
config->runtimePaths = args::getStrings(args, OPT_rpath);
15531586
config->allLoad = args.hasFlag(OPT_all_load, OPT_noall_load, false);
15541587
config->archMultiple = args.hasArg(OPT_arch_multiple);
@@ -1834,11 +1867,20 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
18341867
// explicitly exported. Do this before running LTO so that LTO can better
18351868
// optimize.
18361869
handleExplicitExports();
1870+
1871+
bool didCompileBitcodeFiles = compileBitcodeFiles();
1872+
1873+
// If --thinlto-index-only is given, we should create only "index
1874+
// files" and not object files. Index file creation is already done
1875+
// in compileBitcodeFiles, so we are done if that's the case.
1876+
if (config->thinLTOIndexOnly)
1877+
return errorCount() == 0;
1878+
18371879
// LTO may emit a non-hidden (extern) object file symbol even if the
18381880
// corresponding bitcode symbol is hidden. In particular, this happens for
18391881
// cross-module references to hidden symbols under ThinLTO. Thus, if we
18401882
// compiled any bitcode files, we must redo the symbol hiding.
1841-
if (compileBitcodeFiles())
1883+
if (didCompileBitcodeFiles)
18421884
handleExplicitExports();
18431885
replaceCommonSymbols();
18441886

lld/MachO/InputFiles.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,9 @@ BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
22062206
: InputFile(BitcodeKind, mb, lazy), forceHidden(forceHidden) {
22072207
this->archiveName = std::string(archiveName);
22082208
std::string path = mb.getBufferIdentifier().str();
2209+
if (config->thinLTOIndexOnly)
2210+
path = replaceThinLTOSuffix(mb.getBufferIdentifier());
2211+
22092212
// ThinLTO assumes that all MemoryBufferRefs given to it have a unique
22102213
// name. If two members with the same name are provided, this causes a
22112214
// collision and ThinLTO can't proceed.
@@ -2246,6 +2249,13 @@ void BitcodeFile::parseLazy() {
22462249
}
22472250
}
22482251

2252+
std::string macho::replaceThinLTOSuffix(StringRef path) {
2253+
auto [suffix, repl] = config->thinLTOObjectSuffixReplace;
2254+
if (path.consume_back(suffix))
2255+
return (path + repl).str();
2256+
return std::string(path);
2257+
}
2258+
22492259
void macho::extract(InputFile &file, StringRef reason) {
22502260
if (!file.lazy)
22512261
return;

lld/MachO/InputFiles.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ std::vector<const CommandType *> findCommands(const void *anyHdr,
357357
return detail::findCommands<CommandType>(anyHdr, 0, types...);
358358
}
359359

360+
std::string replaceThinLTOSuffix(StringRef path);
360361
} // namespace macho
361362

362363
std::string toString(const macho::InputFile *file);

lld/MachO/LTO.cpp

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "lld/Common/CommonLinkerContext.h"
1818
#include "lld/Common/Strings.h"
1919
#include "lld/Common/TargetOptionsCommandFlags.h"
20+
#include "llvm/Bitcode/BitcodeWriter.h"
2021
#include "llvm/LTO/Config.h"
2122
#include "llvm/LTO/LTO.h"
2223
#include "llvm/Support/Caching.h"
@@ -31,6 +32,25 @@ using namespace llvm;
3132
using namespace llvm::MachO;
3233
using namespace llvm::sys;
3334

35+
// Creates an empty file to store a list of object files for final
36+
// linking of distributed ThinLTO.
37+
static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) {
38+
std::error_code ec;
39+
auto ret =
40+
std::make_unique<raw_fd_ostream>(file, ec, sys::fs::OpenFlags::OF_None);
41+
if (ec) {
42+
error("cannot open " + file + ": " + ec.message());
43+
return nullptr;
44+
}
45+
return ret;
46+
}
47+
48+
static std::string getThinLTOOutputFile(StringRef modulePath) {
49+
return lto::getThinLTOOutputFile(
50+
std::string(modulePath), std::string(config->thinLTOPrefixReplace.first),
51+
std::string(config->thinLTOPrefixReplace.second));
52+
}
53+
3454
static lto::Config createConfig() {
3555
lto::Config c;
3656
c.Options = initTargetOptionsFromCodeGenFlags();
@@ -44,6 +64,9 @@ static lto::Config createConfig() {
4464
c.PreCodeGenPassesHook = [](legacy::PassManager &pm) {
4565
pm.add(createObjCARCContractPass());
4666
};
67+
68+
c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
69+
4770
c.TimeTraceEnabled = config->timeTraceEnabled;
4871
c.TimeTraceGranularity = config->timeTraceGranularity;
4972
c.OptLevel = config->ltoo;
@@ -67,13 +90,35 @@ static void saveOrHardlinkBuffer(StringRef buffer, const Twine &path,
6790
}
6891

6992
BitcodeCompiler::BitcodeCompiler() {
70-
lto::ThinBackend backend = lto::createInProcessThinBackend(
71-
heavyweight_hardware_concurrency(config->thinLTOJobs));
93+
// Initialize indexFile.
94+
if (!config->thinLTOIndexOnlyArg.empty())
95+
indexFile = openFile(config->thinLTOIndexOnlyArg);
96+
97+
// Initialize ltoObj.
98+
lto::ThinBackend backend;
99+
auto onIndexWrite = [&](StringRef S) { thinIndices.erase(S); };
100+
if (config->thinLTOIndexOnly) {
101+
backend = lto::createWriteIndexesThinBackend(
102+
std::string(config->thinLTOPrefixReplace.first),
103+
std::string(config->thinLTOPrefixReplace.second),
104+
config->thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite);
105+
} else {
106+
backend = lto::createInProcessThinBackend(
107+
llvm::heavyweight_hardware_concurrency(config->thinLTOJobs),
108+
onIndexWrite, config->thinLTOEmitIndexFiles,
109+
config->thinLTOEmitImportsFiles);
110+
}
111+
72112
ltoObj = std::make_unique<lto::LTO>(createConfig(), backend);
73113
}
74114

75115
void BitcodeCompiler::add(BitcodeFile &f) {
76-
ArrayRef<lto::InputFile::Symbol> objSyms = f.obj->symbols();
116+
lto::InputFile &obj = *f.obj;
117+
118+
if (config->thinLTOEmitIndexFiles)
119+
thinIndices.insert(obj.getName());
120+
121+
ArrayRef<lto::InputFile::Symbol> objSyms = obj.symbols();
77122
std::vector<lto::SymbolResolution> resols;
78123
resols.reserve(objSyms.size());
79124

@@ -117,6 +162,37 @@ void BitcodeCompiler::add(BitcodeFile &f) {
117162
checkError(ltoObj->add(std::move(f.obj), resols));
118163
}
119164

165+
// If LazyObjFile has not been added to link, emit empty index files.
166+
// This is needed because this is what GNU gold plugin does and we have a
167+
// distributed build system that depends on that behavior.
168+
static void thinLTOCreateEmptyIndexFiles() {
169+
DenseSet<StringRef> linkedBitCodeFiles;
170+
for (InputFile *file : inputFiles)
171+
if (auto *f = dyn_cast<BitcodeFile>(file))
172+
if (!f->lazy)
173+
linkedBitCodeFiles.insert(f->getName());
174+
175+
for (InputFile *file : inputFiles) {
176+
if (auto *f = dyn_cast<BitcodeFile>(file)) {
177+
if (!f->lazy)
178+
continue;
179+
if (linkedBitCodeFiles.contains(f->getName()))
180+
continue;
181+
std::string path =
182+
replaceThinLTOSuffix(getThinLTOOutputFile(f->obj->getName()));
183+
std::unique_ptr<raw_fd_ostream> os = openFile(path + ".thinlto.bc");
184+
if (!os)
185+
continue;
186+
187+
ModuleSummaryIndex m(/*HaveGVs=*/false);
188+
m.setSkipModuleByDistributedBackend();
189+
writeIndexToFile(m, *os);
190+
if (config->thinLTOEmitImportsFiles)
191+
openFile(path + ".imports");
192+
}
193+
}
194+
}
195+
120196
// Merge all the bitcode files we have seen, codegen the result
121197
// and return the resulting ObjectFile(s).
122198
std::vector<ObjFile *> BitcodeCompiler::compile() {
@@ -142,8 +218,16 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
142218
},
143219
cache));
144220

145-
if (!config->thinLTOCacheDir.empty())
146-
pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
221+
// Emit empty index files for non-indexed files
222+
for (StringRef s : thinIndices) {
223+
std::string path = getThinLTOOutputFile(s);
224+
openFile(path + ".thinlto.bc");
225+
if (config->thinLTOEmitImportsFiles)
226+
openFile(path + ".imports");
227+
}
228+
229+
if (config->thinLTOEmitIndexFiles)
230+
thinLTOCreateEmptyIndexFiles();
147231

148232
// In ThinLTO mode, Clang passes a temporary directory in -object_path_lto,
149233
// while the argument is a single file in FullLTO mode.
@@ -162,6 +246,32 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
162246
}
163247
}
164248

249+
auto outputFilePath = [objPathIsDir](int i) {
250+
SmallString<261> filePath("/tmp/lto.tmp");
251+
if (!config->ltoObjPath.empty()) {
252+
filePath = config->ltoObjPath;
253+
if (objPathIsDir)
254+
path::append(filePath, Twine(i) + "." +
255+
getArchitectureName(config->arch()) +
256+
".lto.o");
257+
}
258+
return filePath;
259+
};
260+
261+
// ThinLTO with index only option is required to generate only the index
262+
// files. After that, we exit from linker and ThinLTO backend runs in a
263+
// distributed environment.
264+
if (config->thinLTOIndexOnly) {
265+
if (!config->ltoObjPath.empty())
266+
saveBuffer(buf[0], outputFilePath(0));
267+
if (indexFile)
268+
indexFile->close();
269+
return {};
270+
}
271+
272+
if (!config->thinLTOCacheDir.empty())
273+
pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
274+
165275
std::vector<ObjFile *> ret;
166276
for (unsigned i = 0; i < maxTasks; ++i) {
167277
// Get the native object contents either from the cache or from memory. Do
@@ -183,14 +293,9 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
183293
saveBuffer(objBuf,
184294
config->outputFile + ((i == 0) ? "" : Twine(i)) + ".lto.o");
185295

186-
SmallString<261> filePath("/tmp/lto.tmp");
296+
auto filePath = outputFilePath(i);
187297
uint32_t modTime = 0;
188298
if (!config->ltoObjPath.empty()) {
189-
filePath = config->ltoObjPath;
190-
if (objPathIsDir)
191-
path::append(filePath, Twine(i) + "." +
192-
getArchitectureName(config->arch()) +
193-
".lto.o");
194299
saveOrHardlinkBuffer(objBuf, filePath, cachePath);
195300
modTime = getModTime(filePath);
196301
}

lld/MachO/LTO.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
#ifndef LLD_MACHO_LTO_H
1010
#define LLD_MACHO_LTO_H
1111

12+
#include "lld/Common/LLVM.h"
13+
#include "llvm/ADT/DenseSet.h"
1214
#include "llvm/ADT/SmallString.h"
1315
#include "llvm/Support/MemoryBuffer.h"
16+
#include "llvm/Support/raw_ostream.h"
1417
#include <memory>
1518
#include <vector>
1619

@@ -34,6 +37,8 @@ class BitcodeCompiler {
3437
std::unique_ptr<llvm::lto::LTO> ltoObj;
3538
std::vector<llvm::SmallString<0>> buf;
3639
std::vector<std::unique_ptr<llvm::MemoryBuffer>> files;
40+
std::unique_ptr<llvm::raw_fd_ostream> indexFile;
41+
llvm::DenseSet<StringRef> thinIndices;
3742
};
3843

3944
} // namespace lld::macho

lld/MachO/Options.td

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,22 @@ def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
2828
def threads_eq : Joined<["--"], "threads=">,
2929
HelpText<"Number of threads. '1' disables multi-threading. By default all available hardware threads are used">,
3030
Group<grp_lld>;
31+
def thinlto_emit_imports_files: Flag<["--"], "thinlto-emit-imports-files">,
32+
Group<grp_lld>;
33+
def thinlto_emit_index_files: Flag<["--"], "thinlto-emit-index-files">,
34+
Group<grp_lld>;
35+
def thinlto_index_only: Flag<["--"], "thinlto-index-only">,
36+
Group<grp_lld>;
37+
def thinlto_index_only_eq: Joined<["--"], "thinlto-index-only=">,
38+
Group<grp_lld>;
3139
def thinlto_jobs_eq : Joined<["--"], "thinlto-jobs=">,
3240
HelpText<"Number of ThinLTO jobs. Default to --threads=">,
3341
Group<grp_lld>;
42+
def thinlto_object_suffix_replace_eq:
43+
Joined<["--"], "thinlto-object-suffix-replace=">,
44+
Group<grp_lld>;
45+
def thinlto_prefix_replace_eq: Joined<["--"], "thinlto-prefix-replace=">,
46+
Group<grp_lld>;
3447
def reproduce: Separate<["--"], "reproduce">,
3548
Group<grp_lld>;
3649
def reproduce_eq: Joined<["--"], "reproduce=">,

lld/test/ELF/lto/thinlto-index-only.ll

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818

1919
;; Ensure lld doesn't generates index files when --thinlto-index-only is not enabled.
2020
; RUN: rm -f 1.o.thinlto.bc 2.o.thinlto.bc
21-
; RUN: ld.lld -shared 1.o 2.o -o 4
21+
; RUN: ld.lld -shared 1.o 2.o -o 5
2222
; RUN: not ls 1.o.thinlto.bc
2323
; RUN: not ls 2.o.thinlto.bc
2424

2525
;; Ensure lld generates an index and not a binary if requested.
26-
; RUN: rm -f 4
2726
; RUN: ld.lld --plugin-opt=thinlto-index-only -shared 1.o 2.o -o 4
2827
; RUN: llvm-bcanalyzer -dump 1.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND1
2928
; RUN: llvm-bcanalyzer -dump 2.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND2
3029
; RUN: not test -e 4
3130

32-
;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib
31+
;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib.
3332
; RUN: rm -f 2.o.thinlto.bc 4
3433
; RUN: ld.lld --plugin-opt=thinlto-index-only -shared 1.o 3.o --start-lib 2.o --end-lib -o 4
3534
; RUN: llvm-dis < 2.o.thinlto.bc | grep -q '\^0 = module:'

lld/test/ELF/lto/thinlto-obj-path.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747

4848
;; Ensure lld does not emit empty combined module in default.
4949
; RUN: rm -fr objpath && mkdir objpath
50-
; RUN: ld.lld 1.bc 2.bc -o objpath/a.out --save-temps
50+
; RUN: ld.lld -shared 1.bc 2.bc -o objpath/a.out --save-temps
5151
; RUN: ls objpath/a.out*.lto.* | count 2
5252

5353
; EMPTY: file format elf64-x86-64

0 commit comments

Comments
 (0)