1//===- Compilation.cpp - Compilation Task 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
9#include "clang/Driver/Compilation.h"
10#include "clang/Basic/LLVM.h"
11#include "clang/Driver/Action.h"
12#include "clang/Driver/CommonArgs.h"
13#include "clang/Driver/Driver.h"
14#include "clang/Driver/Job.h"
15#include "clang/Driver/ToolChain.h"
16#include "clang/Driver/Util.h"
17#include "clang/Options/Options.h"
18#include "llvm/ADT/STLExtras.h"
19#include "llvm/Option/ArgList.h"
20#include "llvm/Option/OptSpecifier.h"
21#include "llvm/Option/Option.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/ThreadPool.h"
24#include "llvm/Support/Threading.h"
25#include "llvm/Support/raw_ostream.h"
26#include "llvm/TargetParser/Triple.h"
27#include <algorithm>
28#include <cassert>
29#include <optional>
30#include <string>
31#include <system_error>
32#include <utility>
33
34using namespace clang;
35using namespace driver;
36using namespace llvm::opt;
37
38Compilation::Compilation(const Driver &D, const ToolChain &_DefaultToolChain,
39 InputArgList *_Args, DerivedArgList *_TranslatedArgs,
40 bool ContainsError)
41 : TheDriver(D), DefaultToolChain(_DefaultToolChain), Args(_Args),
42 TranslatedArgs(_TranslatedArgs), ContainsError(ContainsError) {
43 // The offloading host toolchain is the default toolchain.
44 OrderedOffloadingToolchains.insert(
45 x: std::make_pair(x: Action::OFK_Host, y: &DefaultToolChain));
46}
47
48Compilation::~Compilation() {
49 // Remove temporary files. This must be done before arguments are freed, as
50 // the file names might be derived from the input arguments.
51 if (!TheDriver.isSaveTempsEnabled() && !ForceKeepTempFiles)
52 CleanupFileList(Files: TempFiles);
53
54 delete TranslatedArgs;
55 delete Args;
56
57 // Free any derived arg lists.
58 for (auto Arg : TCArgs)
59 if (Arg.second != TranslatedArgs)
60 delete Arg.second;
61}
62
63const DerivedArgList &
64Compilation::getArgsForToolChain(const ToolChain *TC, BoundArch BA,
65 Action::OffloadKind DeviceOffloadKind) {
66 if (!TC)
67 TC = &DefaultToolChain;
68
69 DerivedArgList *&Entry = TCArgs[{TC, BA, DeviceOffloadKind}];
70 if (!Entry) {
71 SmallVector<Arg *, 4> AllocatedArgs;
72 DerivedArgList *OpenMPArgs = nullptr;
73 // Translate OpenMP toolchain arguments provided via the -Xopenmp-target flags.
74 if (DeviceOffloadKind == Action::OFK_OpenMP) {
75 const ToolChain *HostTC = getSingleOffloadToolChain<Action::OFK_Host>();
76 bool SameTripleAsHost = (TC->getTriple() == HostTC->getTriple());
77 OpenMPArgs = TC->TranslateOpenMPTargetArgs(
78 Args: *TranslatedArgs, SameTripleAsHost, AllocatedArgs);
79 }
80
81 DerivedArgList *NewDAL = nullptr;
82 if (!OpenMPArgs) {
83 NewDAL = TC->TranslateXarchArgs(Args: *TranslatedArgs, BA, DeviceOffloadKind,
84 AllocatedArgs: &AllocatedArgs);
85 } else {
86 NewDAL = TC->TranslateXarchArgs(Args: *OpenMPArgs, BA, DeviceOffloadKind,
87 AllocatedArgs: &AllocatedArgs);
88 if (!NewDAL)
89 NewDAL = OpenMPArgs;
90 else
91 delete OpenMPArgs;
92 }
93
94 if (!NewDAL) {
95 Entry = TC->TranslateArgs(Args: *TranslatedArgs, BA, DeviceOffloadKind);
96 if (!Entry)
97 Entry = TranslatedArgs;
98 } else {
99 Entry = TC->TranslateArgs(Args: *NewDAL, BA, DeviceOffloadKind);
100 if (!Entry)
101 Entry = NewDAL;
102 else
103 delete NewDAL;
104 }
105
106 // Add allocated arguments to the final DAL.
107 for (auto *ArgPtr : AllocatedArgs)
108 Entry->AddSynthesizedArg(A: ArgPtr);
109 }
110
111 return *Entry;
112}
113
114bool Compilation::CleanupFile(const char *File, bool IssueErrors) const {
115 // FIXME: Why are we trying to remove files that we have not created? For
116 // example we should only try to remove a temporary assembly file if
117 // "clang -cc1" succeed in writing it. Was this a workaround for when
118 // clang was writing directly to a .s file and sometimes leaving it behind
119 // during a failure?
120
121 // FIXME: If this is necessary, we can still try to split
122 // llvm::sys::fs::remove into a removeFile and a removeDir and avoid the
123 // duplicated stat from is_regular_file.
124
125 // Don't try to remove files which we don't have write access to (but may be
126 // able to remove), or non-regular files. Underlying tools may have
127 // intentionally not overwritten them.
128 if (!llvm::sys::fs::can_write(Path: File) || !llvm::sys::fs::is_regular_file(Path: File))
129 return true;
130
131 if (std::error_code EC = llvm::sys::fs::remove(path: File)) {
132 // Failure is only failure if the file exists and is "regular". We checked
133 // for it being regular before, and llvm::sys::fs::remove ignores ENOENT,
134 // so we don't need to check again.
135
136 if (IssueErrors)
137 getDriver().Diag(DiagID: diag::err_drv_unable_to_remove_file)
138 << EC.message();
139 return false;
140 }
141 return true;
142}
143
144bool Compilation::CleanupFileList(const llvm::opt::ArgStringList &Files,
145 bool IssueErrors) const {
146 bool Success = true;
147 for (const auto &File: Files)
148 Success &= CleanupFile(File, IssueErrors);
149 return Success;
150}
151
152bool Compilation::CleanupFileMap(const ArgStringMap &Files,
153 const JobAction *JA,
154 bool IssueErrors) const {
155 bool Success = true;
156 for (const auto &File : Files) {
157 // If specified, only delete the files associated with the JobAction.
158 // Otherwise, delete all files in the map.
159 if (JA && File.first != JA)
160 continue;
161 Success &= CleanupFile(File: File.second, IssueErrors);
162 }
163 return Success;
164}
165
166int Compilation::ExecuteCommand(const Command &C,
167 const Command *&FailingCommand,
168 bool LogOnly) const {
169 if ((getDriver().CCPrintOptions ||
170 getArgs().hasArg(Ids: options::OPT_v)) && !getDriver().CCGenDiagnostics) {
171 raw_ostream *OS = &llvm::errs();
172 std::unique_ptr<llvm::raw_fd_ostream> OwnedStream;
173
174 // Follow gcc implementation of CC_PRINT_OPTIONS; we could also cache the
175 // output stream.
176 if (getDriver().CCPrintOptions &&
177 !getDriver().CCPrintOptionsFilename.empty()) {
178 std::error_code EC;
179 OwnedStream.reset(p: new llvm::raw_fd_ostream(
180 getDriver().CCPrintOptionsFilename, EC,
181 llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF));
182 if (EC) {
183 getDriver().Diag(DiagID: diag::err_drv_cc_print_options_failure)
184 << EC.message();
185 FailingCommand = &C;
186 return 1;
187 }
188 OS = OwnedStream.get();
189 }
190
191 if (getDriver().CCPrintOptions)
192 *OS << "[Logging clang options]\n";
193
194 C.Print(OS&: *OS, Terminator: "\n", /*Quote=*/getDriver().CCPrintOptions);
195 }
196
197 if (LogOnly)
198 return 0;
199
200 std::string Error;
201 bool ExecutionFailed;
202 int Res = C.Execute(Redirects, ErrMsg: &Error, ExecutionFailed: &ExecutionFailed);
203 if (PostCallback)
204 PostCallback(C, Res);
205 if (!Error.empty()) {
206 assert(Res && "Error string set with 0 result code!");
207 getDriver().Diag(DiagID: diag::err_drv_command_failure) << Error;
208 }
209
210 if (Res)
211 FailingCommand = &C;
212
213 return ExecutionFailed ? 1 : Res;
214}
215
216using FailingCommandList = SmallVectorImpl<std::pair<int, const Command *>>;
217
218static bool ActionFailed(const Action *A,
219 const FailingCommandList &FailingCommands) {
220 if (FailingCommands.empty())
221 return false;
222
223 // CUDA/HIP/SYCL can have the same input source code compiled multiple times
224 // so do not compile again if there are already failures. It is OK to abort
225 // the CUDA/HIP/SYCL pipeline on errors.
226 if (A->isOffloading(OKind: Action::OFK_Cuda) || A->isOffloading(OKind: Action::OFK_HIP) ||
227 A->isOffloading(OKind: Action::OFK_SYCL))
228 return true;
229
230 for (const auto &CI : FailingCommands)
231 if (A == &(CI.second->getSource()))
232 return true;
233
234 for (const auto *AI : A->inputs())
235 if (ActionFailed(A: AI, FailingCommands))
236 return true;
237
238 return false;
239}
240
241static bool ActionDependsOn(const Action *A, const Action *Other) {
242 return A == Other || llvm::any_of(Range: A->inputs(), P: [&](const Action *Input) {
243 return ActionDependsOn(A: Input, Other);
244 });
245}
246
247static bool ActionsAreIndependent(const Action *A, const Action *B) {
248 return !ActionDependsOn(A, Other: B) && !ActionDependsOn(A: B, Other: A);
249}
250
251static bool CanRunInParallelOffloadJobGroup(const Command &Job) {
252 return !Job.InProcess && !Job.PrintInputFilenames &&
253 !Job.getBoundArch().empty() &&
254 !Job.getOffloadDeviceParallelJobGroup().empty();
255}
256
257static bool SameParallelOffloadJobGroup(const Command &A, const Command &B) {
258 return A.getOffloadDeviceParallelJobGroup() ==
259 B.getOffloadDeviceParallelJobGroup();
260}
261
262static bool HasDistinctBoundArch(const Command &Candidate,
263 ArrayRef<const Command *> Jobs) {
264 BoundArch CandidateArch = Candidate.getBoundArch();
265 return llvm::none_of(Range&: Jobs, P: [&](const Command *Job) {
266 return Job->getBoundArch() == CandidateArch;
267 });
268}
269
270static std::optional<llvm::ThreadPoolStrategy>
271getParallelOffloadJobsStrategy(const ArgList &Args, unsigned NumJobs) {
272 if (NumJobs < 2)
273 return std::nullopt;
274
275 auto OffloadJobs = tools::parseOffloadJobs(Args);
276 if (!OffloadJobs.isValid())
277 return std::nullopt;
278
279 if (OffloadJobs.K == tools::OffloadJobsOpt::Kind::Jobserver)
280 return llvm::jobserver_concurrency();
281
282 if (OffloadJobs.NumThreads < 2)
283 return std::nullopt;
284
285 return llvm::hardware_concurrency(ThreadCount: std::min(a: OffloadJobs.NumThreads, b: NumJobs));
286}
287
288struct ParallelJobResult {
289 int Res = 0;
290 bool ExecutionFailed = false;
291 std::string Error;
292};
293
294struct ParallelOffloadJobGroupResult {
295 size_t NumJobs = 0;
296};
297
298static std::optional<ParallelOffloadJobGroupResult>
299tryExecuteParallelOffloadJobGroup(const Driver &D, const ArgList &Args,
300 ArrayRef<std::optional<StringRef>> Redirects,
301 const JobList::list_type &JobStorage,
302 size_t StartIndex,
303 FailingCommandList &FailingCommands) {
304 const Command &Job = *JobStorage[StartIndex];
305 if (!CanRunInParallelOffloadJobGroup(Job))
306 return std::nullopt;
307
308 SmallVector<const Command *, 4> ParallelJobs;
309 for (size_t I = StartIndex; I < JobStorage.size(); ++I) {
310 const Command &Candidate = *JobStorage[I];
311 if (ActionFailed(A: &Candidate.getSource(), FailingCommands))
312 break;
313
314 if (!CanRunInParallelOffloadJobGroup(Job: Candidate))
315 break;
316
317 if (!SameParallelOffloadJobGroup(A: Job, B: Candidate))
318 break;
319
320 if (!HasDistinctBoundArch(Candidate, Jobs: ParallelJobs))
321 break;
322
323 if (!llvm::all_of(Range&: ParallelJobs, P: [&](const Command *Other) {
324 return ActionsAreIndependent(A: &Candidate.getSource(),
325 B: &Other->getSource());
326 }))
327 break;
328
329 ParallelJobs.push_back(Elt: &Candidate);
330 }
331
332 std::optional<llvm::ThreadPoolStrategy> Strategy =
333 getParallelOffloadJobsStrategy(Args, NumJobs: ParallelJobs.size());
334 if (!Strategy)
335 return std::nullopt;
336
337 SmallVector<ParallelJobResult, 4> Results(ParallelJobs.size());
338 llvm::DefaultThreadPool Pool(*Strategy);
339 for (auto IndexedJob : llvm::enumerate(First&: ParallelJobs)) {
340 size_t Index = IndexedJob.index();
341 const Command *ParallelJob = IndexedJob.value();
342 Pool.async(F: [&, Index, ParallelJob] {
343 Results[Index].Res = ParallelJob->Execute(
344 Redirects, ErrMsg: &Results[Index].Error, ExecutionFailed: &Results[Index].ExecutionFailed);
345 });
346 }
347 Pool.wait();
348
349 for (auto [Index, ParallelJob] : llvm::enumerate(First&: ParallelJobs)) {
350 ParallelJobResult &Result = Results[Index];
351 if (!Result.Error.empty()) {
352 assert(Result.Res && "Error string set with 0 result code!");
353 D.Diag(DiagID: diag::err_drv_command_failure) << Result.Error;
354 }
355
356 if (Result.Res) {
357 FailingCommands.push_back(
358 Elt: std::make_pair(x: Result.ExecutionFailed ? 1 : Result.Res, y&: ParallelJob));
359 }
360 }
361
362 return ParallelOffloadJobGroupResult{.NumJobs: ParallelJobs.size()};
363}
364
365void Compilation::ExecuteJobs(const JobList &Jobs,
366 FailingCommandList &FailingCommands,
367 bool LogOnly) const {
368 // According to UNIX standard, driver need to continue compiling all the
369 // inputs on the command line even one of them failed.
370 // In all but CLMode, execute all the jobs unless the necessary inputs for the
371 // job is missing due to previous failures.
372 bool CanRunJobsInParallel =
373 !LogOnly && !getDriver().CCPrintOptions &&
374 !getDriver().CCPrintProcessStats && !getDriver().CCGenDiagnostics &&
375 !getDriver().IsCLMode() && !getArgs().hasArg(Ids: options::OPT_v) &&
376 Redirects.empty() && !PostCallback;
377
378 const auto &JobStorage = Jobs.getJobs();
379 for (size_t I = 0; I < JobStorage.size();) {
380 const auto &Job = *JobStorage[I];
381 if (ActionFailed(A: &Job.getSource(), FailingCommands)) {
382 ++I;
383 continue;
384 }
385
386 if (CanRunJobsInParallel) {
387 if (std::optional<ParallelOffloadJobGroupResult> Result =
388 tryExecuteParallelOffloadJobGroup(D: getDriver(), Args: getArgs(),
389 Redirects, JobStorage, StartIndex: I,
390 FailingCommands)) {
391 I += Result->NumJobs;
392 continue;
393 }
394 }
395
396 const Command *FailingCommand = nullptr;
397 if (int Res = ExecuteCommand(C: Job, FailingCommand, LogOnly)) {
398 FailingCommands.push_back(Elt: std::make_pair(x&: Res, y&: FailingCommand));
399 // Bail as soon as one command fails in cl driver mode.
400 if (TheDriver.IsCLMode())
401 return;
402 }
403 ++I;
404 }
405}
406
407void Compilation::initCompilationForDiagnostics() {
408 ForDiagnostics = true;
409
410 // Free actions and jobs.
411 Actions.clear();
412 AllActions.clear();
413 Jobs.clear();
414
415 // Remove temporary files.
416 if (!TheDriver.isSaveTempsEnabled() && !ForceKeepTempFiles)
417 CleanupFileList(Files: TempFiles);
418
419 // Clear temporary/results file lists.
420 TempFiles.clear();
421 ResultFiles.clear();
422 FailureResultFiles.clear();
423
424 // Remove any user specified output. Claim any unclaimed arguments, so as
425 // to avoid emitting warnings about unused args.
426 OptSpecifier OutputOpts[] = {
427 options::OPT_o, options::OPT_MD, options::OPT_MMD, options::OPT_M,
428 options::OPT_MM, options::OPT_MF, options::OPT_MG, options::OPT_MJ,
429 options::OPT_MQ, options::OPT_MT, options::OPT_MV};
430 for (const auto &Opt : OutputOpts) {
431 if (TranslatedArgs->hasArg(Ids: Opt))
432 TranslatedArgs->eraseArg(Id: Opt);
433 }
434 TranslatedArgs->ClaimAllArgs();
435
436 // Force re-creation of the toolchain Args, otherwise our modifications just
437 // above will have no effect.
438 for (auto Arg : TCArgs)
439 if (Arg.second != TranslatedArgs)
440 delete Arg.second;
441 TCArgs.clear();
442
443 // Redirect stdout/stderr to /dev/null.
444 Redirects = {std::nullopt, {""}, {""}};
445
446 // Temporary files added by diagnostics should be kept.
447 ForceKeepTempFiles = true;
448}
449
450StringRef Compilation::getSysRoot() const {
451 return getDriver().SysRoot;
452}
453
454void Compilation::Redirect(ArrayRef<std::optional<StringRef>> Redirects) {
455 this->Redirects = Redirects;
456}
457