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