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