1//===- DTLTO.cpp - Integrated Distributed ThinLTO implementation ----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8// \file
9// This file implements support functions for Integrated Distributed ThinLTO,
10// focusing on preparing complilation jobs for distribution.
11//
12//===----------------------------------------------------------------------===//
13
14#include "llvm/DTLTO/DTLTO.h"
15
16#include "llvm/ADT/STLExtras.h"
17#include "llvm/ADT/ScopeExit.h"
18#include "llvm/ADT/StringExtras.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/LTO/LTO.h"
21#include "llvm/Support/FileSystem.h"
22#include "llvm/Support/MemoryBufferRef.h"
23#include "llvm/Support/Path.h"
24#include "llvm/Support/Process.h"
25#include "llvm/Support/TimeProfiler.h"
26#include "llvm/Support/raw_ostream.h"
27
28#include <string>
29#include <system_error>
30#include <utility>
31#include <vector>
32
33using namespace llvm;
34
35// Experimentation showed that serial deletion is most efficient, hence
36// a single thread.
37lto::DTLTO::BackgroundDeletion::BackgroundDeletion()
38 : DefaultThreadPool(hardware_concurrency(ThreadCount: 1)) {}
39
40lto::DTLTO::BackgroundDeletion::~BackgroundDeletion() { waitForTasks(); }
41
42void lto::DTLTO::BackgroundDeletion::waitForTasks() {
43 wait();
44 for (const std::string &Warning : Warnings)
45 errs() << "warning: could not remove the file " << Warning << "\n";
46 Warnings.clear();
47}
48
49void lto::DTLTO::BackgroundDeletion::removeFiles(
50 std::vector<std::string> &&Files, const Config &Conf) {
51 if (Files.empty())
52 return;
53
54 async(F: [this, Files = std::move(Files), TTE = Conf.TimeTraceEnabled,
55 TTG = Conf.TimeTraceGranularity] {
56 if (LLVM_ENABLE_THREADS && TTE)
57 timeTraceProfilerInitialize(TimeTraceGranularity: TTG, ProcName: "Remove DTLTO temporary files");
58 {
59 TimeTraceScope TimeScope("Remove DTLTO temporary files");
60 for (const std::string &Path : Files) {
61 std::error_code EC = sys::fs::remove(path: Path, IgnoreNonExisting: true);
62 if (!EC ||
63 EC == std::make_error_code(e: std::errc::no_such_file_or_directory))
64 continue;
65
66 Warnings.emplace_back(args: "'" + Path + "': " + EC.message());
67 }
68 }
69 if (LLVM_ENABLE_THREADS && TTE)
70 timeTraceProfilerFinishThread();
71 });
72}
73
74void lto::DTLTO::waitForCleanup() { BackgroundDeleter.waitForTasks(); }
75
76// Remove temporary files created to enable distribution.
77void lto::DTLTO::cleanup() {
78 if (SaveTemps)
79 return;
80
81 BackgroundDeleter.removeFiles(Files: std::move(CleanupList), Conf);
82}
83
84// Runs the DTLTO thin link phase, producing per-module summary indices,
85// import lists, and cache keys for distribution.
86Error lto::DTLTO::performThinLink() {
87 size_t NumTasks = getMaxTasks();
88 SummaryIndexFiles.resize(new_size: NumTasks);
89 ImportsFilesList.resize(new_size: NumTasks);
90 CacheKeysList.resize(new_size: NumTasks);
91
92 lto::Config &Cfg = getConfig();
93 Cfg.GetSummaryIndexOutputStream =
94 [&](size_t task) -> std::unique_ptr<raw_svector_ostream> {
95 return std::make_unique<raw_svector_ostream>(args&: SummaryIndexFiles[task]);
96 };
97 Cfg.GetCacheKeyOutputString = [&](size_t task) -> std::string & {
98 return CacheKeysList[task];
99 };
100 Cfg.GetImportsListOutputArray =
101 [&](size_t task) -> std::vector<std::string> & {
102 return ImportsFilesList[task];
103 };
104 return Base::run(AddStream: AddStreamFunc, Cache: {});
105}
106
107// Runs the DTLTO pipeline.
108LLVM_ABI Error lto::DTLTO::run(AddStreamFn AddStream, FileCache CacheParam) {
109 scope_exit CleanUp([this]() { cleanup(); });
110
111 AddStreamFunc = AddStream;
112 Cache = std::move(CacheParam);
113 Conf.Dtlto = 1;
114 UID = itostr(X: sys::Process::getProcessId());
115
116 if (Error Err = performThinLink())
117 return Err;
118
119 ThinLTOTaskOffset = RegularLTO.ParallelCodeGenParallelismLevel;
120 DistributorParams.TargetTriple = RegularLTO.CombinedModule->getTargetTriple();
121
122 if (Error Err = prepareDtltoJobs())
123 return Err;
124 if (Error Err = extractLTOInputs())
125 return Err;
126 if (Error Err = performCodegen())
127 return Err;
128 if (Error Err = addObjectFilesToLink())
129 return Err;
130 return Error::success();
131}
132
133// Probes the LTO cache for a compiled native object for the given job.
134Error lto::DTLTO::checkCacheHit(Job &J) {
135 if (!Cache.isValid())
136 return Error::success();
137
138 TimeTraceScope TimeScope("Check cache for DTLTO", J.SummaryIndexPath);
139
140 auto CacheAddStreamExp = Cache(J.Task, J.CacheKey, J.ModuleID);
141 if (Error Err = CacheAddStreamExp.takeError())
142 return Err;
143 AddStreamFn &CacheAddStream = *CacheAddStreamExp;
144 // If CacheAddStream is null, we have a cache hit and at this point
145 // object file is already passed back to the linker.
146 if (!CacheAddStream) {
147 J.Cached = true; // Cache hit, mark the job as cached.
148 CachedJobs.fetch_add(i: 1);
149 } else {
150 // If CacheAddStream is not null, we have a cache miss and we need to
151 // run the backend for codegen. Save cache 'add stream'
152 // function for a later use.
153 J.CacheAddStream = std::move(CacheAddStream);
154 }
155 return Error::success();
156}
157
158// Prepares a single DTLTO backend compilation job for a ThinLTO module.
159Error lto::DTLTO::prepareDtltoJob(StringRef ModulePath, unsigned Task) {
160 assert(Task >= ThinLTOTaskOffset && Task - ThinLTOTaskOffset < Jobs.size() &&
161 "Task index out of range for Jobs");
162 assert(Task < SummaryIndexFiles.size() && "Task index out of range");
163
164 SString ObjFilePath =
165 sys::path::parent_path(path: DistributorParams.LinkerOutputFile);
166 sys::path::append(path&: ObjFilePath, a: sys::path::stem(path: ModulePath) + "." +
167 itostr(X: Task) + "." + UID + ".native.o");
168
169 SString SummaryIndexPathStr = ObjFilePath;
170 SummaryIndexPathStr += ".thinlto.bc";
171 SString ImportsPathStr = ModulePath;
172 ImportsPathStr += ".imports";
173
174 Job &J = Jobs[Task - ThinLTOTaskOffset];
175 J = {.Task: Task,
176 .ModuleID: ModulePath,
177 .NativeObjectPath: Saver.save(S: ObjFilePath.str()),
178 .SummaryIndexPath: Saver.save(S: SummaryIndexPathStr.str()),
179 .ImportsPath: Saver.save(S: ImportsPathStr.str()),
180 .ImportsFilesList: ImportsFilesList[Task],
181 .CacheKey: CacheKeysList[Task],
182 .CacheAddStream: nullptr,
183 .Cached: false};
184
185 if (Error Err = checkCacheHit(J))
186 return Err;
187 if (!J.Cached) {
188 InputModuleIDsToExtract.insert(V: J.ModuleID);
189 for (StringRef ImportPath : J.ImportsFilesList)
190 InputModuleIDsToExtract.insert(V: ImportPath);
191
192 TimeTraceScope JobScope("Emit individual index for DTLTO",
193 J.SummaryIndexPath);
194 if (Error Err = save(Buffer: SummaryIndexFiles[Task], Path: J.SummaryIndexPath))
195 return Err;
196 }
197 if (OnIndexWriteCb)
198 OnIndexWriteCb(J.SummaryIndexPath.str());
199
200 if (ShouldEmitImportFiles)
201 if (Error Err = save(Buffer: join(R&: J.ImportsFilesList, Separator: "\n"), Path: J.ImportsPath))
202 return Err;
203
204 if (!SaveTemps) {
205 if (!J.Cached)
206 addToCleanup(Filename: J.NativeObjectPath.str());
207 if (!ShouldEmitIndexFiles)
208 addToCleanup(Filename: J.SummaryIndexPath.str());
209 if (!ShouldEmitImportFiles)
210 addToCleanup(Filename: J.ImportsPath.str());
211 }
212 return Error::success();
213}
214
215// Derive a set of Clang options that will be shared/common for all DTLTO
216// backend compilations.
217void lto::DTLTO::buildCommonRemoteCompilerOptions() {
218 const lto::Config &C = getConfig();
219 auto &Ops = DistributorParams.CodegenOptions;
220
221 Ops.push_back(Elt: Saver.save(S: "-O" + Twine(C.OptLevel)));
222
223 if (C.Options.EmitAddrsig)
224 Ops.push_back(Elt: "-faddrsig");
225 if (C.Options.FunctionSections)
226 Ops.push_back(Elt: "-ffunction-sections");
227 if (C.Options.DataSections)
228 Ops.push_back(Elt: "-fdata-sections");
229 if (C.PTO.LoopInterchange)
230 Ops.push_back(Elt: "-floop-interchange");
231
232 if (C.RelocModel == Reloc::PIC_)
233 // Clang doesn't have -fpic for all triples.
234 if (!DistributorParams.TargetTriple.isOSBinFormatCOFF())
235 Ops.push_back(Elt: "-fpic");
236
237 // Turn on/off warnings about profile cfg mismatch (default on)
238 // --lto-pgo-warn-mismatch.
239 if (!C.PGOWarnMismatch) {
240 Ops.push_back(Elt: "-mllvm");
241 Ops.push_back(Elt: "-no-pgo-warn-mismatch");
242 }
243
244 // Enable sample-based profile guided optimizations.
245 // Sample profile file path --lto-sample-profile=<value>.
246 if (!C.SampleProfile.empty()) {
247 Ops.push_back(Elt: Saver.save(S: "-fprofile-sample-use=" + Twine(C.SampleProfile)));
248 DistributorParams.CommonInputs.insert(V: C.SampleProfile);
249 }
250
251 // We don't know which of options will be used by Clang.
252 Ops.push_back(Elt: "-Wno-unused-command-line-argument");
253
254 // Forward any supplied options.
255 if (!DistributorParams.RemoteCompilerArgs.empty())
256 for (auto &a : DistributorParams.RemoteCompilerArgs)
257 Ops.push_back(Elt: a);
258}
259
260// Initializes DTLTO state and prepares a job for each ThinLTO module.
261Error lto::DTLTO::prepareDtltoJobs() {
262 auto &ModuleMap =
263 ThinLTO.ModulesToCompile ? *ThinLTO.ModulesToCompile : ThinLTO.ModuleMap;
264
265 InputModuleIDsToExtract.clear();
266
267 if (ModuleMap.empty())
268 return Error::success();
269
270 Jobs.resize(N: ModuleMap.size());
271
272 for (auto [I, Mod] : enumerate(First&: ModuleMap))
273 if (Error E = prepareDtltoJob(ModulePath: Mod.first, Task: ThinLTOTaskOffset + I))
274 return E;
275
276 return Error::success();
277}
278
279// Runs the DTLTO code generation phase. Must be invoked after thinLink().
280Error lto::DTLTO::performCodegen() {
281 if (Jobs.empty())
282 return Error::success();
283 // Build common remote compiler options.
284 buildCommonRemoteCompilerOptions();
285
286 DistributionDriver Distributor(DistributorParams, Jobs, SaveTemps,
287 [&](StringRef S) { addToCleanup(Filename: S); });
288
289 if (CachedJobs.load() < Jobs.size()) {
290 if (Error E = Distributor())
291 return E;
292 }
293 return Error::success();
294}
295
296// Adds compiled object files to the link for each non-cached job.
297Error lto::DTLTO::addObjectFilesToLink() {
298 TimeTraceScope FilesScope("Add DTLTO files to the link");
299 for (auto &Job : Jobs) {
300 if (!Job.CacheKey.empty() && Job.Cached) {
301 assert(Cache.isValid());
302 continue;
303 }
304 // Load the native object from a file into a memory buffer
305 // and store its contents in the output buffer.
306 auto ObjFileMbOrErr =
307 MemoryBuffer::getFile(Filename: Job.NativeObjectPath, /*IsText=*/false,
308 /*RequiresNullTerminator=*/false);
309 if (std::error_code EC = ObjFileMbOrErr.getError())
310 return make_error<StringError>(
311 Args: BCError + "cannot open native object file: " + Job.NativeObjectPath +
312 ": " + EC.message(),
313 Args: inconvertibleErrorCode());
314
315 MemoryBufferRef ObjFileMbRef = ObjFileMbOrErr->get()->getMemBufferRef();
316 if (Cache.isValid()) {
317 // Cache hits are taken care of earlier. At this point, we could only
318 // have cache misses.
319 assert(Job.CacheAddStream);
320 // Obtain a file stream for a storing a cache entry.
321 auto CachedFileStreamOrErr = Job.CacheAddStream(Job.Task, Job.ModuleID);
322 if (!CachedFileStreamOrErr)
323 return joinErrors(
324 E1: CachedFileStreamOrErr.takeError(),
325 E2: createStringError(EC: inconvertibleErrorCode(),
326 Fmt: "Cannot get a cache file stream: %s",
327 Vals: Job.NativeObjectPath.data()));
328
329 auto &CacheStream = *(CachedFileStreamOrErr->get());
330
331 // This object file will be renamed into cache entry file. The file
332 // memory buffer will be added to lld list of object files.
333 if (Error Err = CacheStream.commit(MemBuf: std::move(ObjFileMbOrErr.get())))
334 return Err;
335 } else {
336 if (AddBuffer) {
337 AddBuffer(Job.Task, Job.ModuleID, std::move(ObjFileMbOrErr.get()));
338 } else {
339 auto StreamOrErr = AddStreamFunc(Job.Task, Job.ModuleID);
340 if (Error Err = StreamOrErr.takeError())
341 return Err;
342 auto &Stream = *StreamOrErr->get();
343 *Stream.OS << ObjFileMbRef.getBuffer();
344 if (Error Err = Stream.commit())
345 return Err;
346 }
347 }
348 }
349 return Error::success();
350}
351