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/Driver.h"
13#include "clang/Driver/Job.h"
14#include "clang/Driver/Options.h"
15#include "clang/Driver/ToolChain.h"
16#include "clang/Driver/Util.h"
17#include "llvm/Option/ArgList.h"
18#include "llvm/Option/OptSpecifier.h"
19#include "llvm/Option/Option.h"
20#include "llvm/Support/FileSystem.h"
21#include "llvm/Support/raw_ostream.h"
22#include "llvm/TargetParser/Triple.h"
23#include <cassert>
24#include <string>
25#include <system_error>
26#include <utility>
27
28using namespace clang;
29using namespace driver;
30using namespace llvm::opt;
31
32Compilation::Compilation(const Driver &D, const ToolChain &_DefaultToolChain,
33 InputArgList *_Args, DerivedArgList *_TranslatedArgs,
34 bool ContainsError)
35 : TheDriver(D), DefaultToolChain(_DefaultToolChain), Args(_Args),
36 TranslatedArgs(_TranslatedArgs), ContainsError(ContainsError) {
37 // The offloading host toolchain is the default toolchain.
38 OrderedOffloadingToolchains.insert(
39 x: std::make_pair(x: Action::OFK_Host, y: &DefaultToolChain));
40}
41
42Compilation::~Compilation() {
43 // Remove temporary files. This must be done before arguments are freed, as
44 // the file names might be derived from the input arguments.
45 if (!TheDriver.isSaveTempsEnabled() && !ForceKeepTempFiles)
46 CleanupFileList(Files: TempFiles);
47
48 delete TranslatedArgs;
49 delete Args;
50
51 // Free any derived arg lists.
52 for (auto Arg : TCArgs)
53 if (Arg.second != TranslatedArgs)
54 delete Arg.second;
55}
56
57const DerivedArgList &
58Compilation::getArgsForToolChain(const ToolChain *TC, StringRef BoundArch,
59 Action::OffloadKind DeviceOffloadKind) {
60 if (!TC)
61 TC = &DefaultToolChain;
62
63 DerivedArgList *&Entry = TCArgs[{TC, BoundArch, DeviceOffloadKind}];
64 if (!Entry) {
65 SmallVector<Arg *, 4> AllocatedArgs;
66 DerivedArgList *OpenMPArgs = nullptr;
67 // Translate OpenMP toolchain arguments provided via the -Xopenmp-target flags.
68 if (DeviceOffloadKind == Action::OFK_OpenMP) {
69 const ToolChain *HostTC = getSingleOffloadToolChain<Action::OFK_Host>();
70 bool SameTripleAsHost = (TC->getTriple() == HostTC->getTriple());
71 OpenMPArgs = TC->TranslateOpenMPTargetArgs(
72 Args: *TranslatedArgs, SameTripleAsHost, AllocatedArgs);
73 }
74
75 DerivedArgList *NewDAL = nullptr;
76 if (!OpenMPArgs) {
77 NewDAL = TC->TranslateXarchArgs(Args: *TranslatedArgs, BoundArch,
78 DeviceOffloadKind, AllocatedArgs: &AllocatedArgs);
79 } else {
80 NewDAL = TC->TranslateXarchArgs(Args: *OpenMPArgs, BoundArch, DeviceOffloadKind,
81 AllocatedArgs: &AllocatedArgs);
82 if (!NewDAL)
83 NewDAL = OpenMPArgs;
84 else
85 delete OpenMPArgs;
86 }
87
88 if (!NewDAL) {
89 Entry = TC->TranslateArgs(Args: *TranslatedArgs, BoundArch, DeviceOffloadKind);
90 if (!Entry)
91 Entry = TranslatedArgs;
92 } else {
93 Entry = TC->TranslateArgs(Args: *NewDAL, BoundArch, DeviceOffloadKind);
94 if (!Entry)
95 Entry = NewDAL;
96 else
97 delete NewDAL;
98 }
99
100 // Add allocated arguments to the final DAL.
101 for (auto *ArgPtr : AllocatedArgs)
102 Entry->AddSynthesizedArg(A: ArgPtr);
103 }
104
105 return *Entry;
106}
107
108bool Compilation::CleanupFile(const char *File, bool IssueErrors) const {
109 // FIXME: Why are we trying to remove files that we have not created? For
110 // example we should only try to remove a temporary assembly file if
111 // "clang -cc1" succeed in writing it. Was this a workaround for when
112 // clang was writing directly to a .s file and sometimes leaving it behind
113 // during a failure?
114
115 // FIXME: If this is necessary, we can still try to split
116 // llvm::sys::fs::remove into a removeFile and a removeDir and avoid the
117 // duplicated stat from is_regular_file.
118
119 // Don't try to remove files which we don't have write access to (but may be
120 // able to remove), or non-regular files. Underlying tools may have
121 // intentionally not overwritten them.
122 if (!llvm::sys::fs::can_write(Path: File) || !llvm::sys::fs::is_regular_file(Path: File))
123 return true;
124
125 if (std::error_code EC = llvm::sys::fs::remove(path: File)) {
126 // Failure is only failure if the file exists and is "regular". We checked
127 // for it being regular before, and llvm::sys::fs::remove ignores ENOENT,
128 // so we don't need to check again.
129
130 if (IssueErrors)
131 getDriver().Diag(DiagID: diag::err_drv_unable_to_remove_file)
132 << EC.message();
133 return false;
134 }
135 return true;
136}
137
138bool Compilation::CleanupFileList(const llvm::opt::ArgStringList &Files,
139 bool IssueErrors) const {
140 bool Success = true;
141 for (const auto &File: Files)
142 Success &= CleanupFile(File, IssueErrors);
143 return Success;
144}
145
146bool Compilation::CleanupFileMap(const ArgStringMap &Files,
147 const JobAction *JA,
148 bool IssueErrors) const {
149 bool Success = true;
150 for (const auto &File : Files) {
151 // If specified, only delete the files associated with the JobAction.
152 // Otherwise, delete all files in the map.
153 if (JA && File.first != JA)
154 continue;
155 Success &= CleanupFile(File: File.second, IssueErrors);
156 }
157 return Success;
158}
159
160int Compilation::ExecuteCommand(const Command &C,
161 const Command *&FailingCommand,
162 bool LogOnly) const {
163 if ((getDriver().CCPrintOptions ||
164 getArgs().hasArg(Ids: options::OPT_v)) && !getDriver().CCGenDiagnostics) {
165 raw_ostream *OS = &llvm::errs();
166 std::unique_ptr<llvm::raw_fd_ostream> OwnedStream;
167
168 // Follow gcc implementation of CC_PRINT_OPTIONS; we could also cache the
169 // output stream.
170 if (getDriver().CCPrintOptions &&
171 !getDriver().CCPrintOptionsFilename.empty()) {
172 std::error_code EC;
173 OwnedStream.reset(p: new llvm::raw_fd_ostream(
174 getDriver().CCPrintOptionsFilename, EC,
175 llvm::sys::fs::OF_Append | llvm::sys::fs::OF_TextWithCRLF));
176 if (EC) {
177 getDriver().Diag(DiagID: diag::err_drv_cc_print_options_failure)
178 << EC.message();
179 FailingCommand = &C;
180 return 1;
181 }
182 OS = OwnedStream.get();
183 }
184
185 if (getDriver().CCPrintOptions)
186 *OS << "[Logging clang options]\n";
187
188 C.Print(OS&: *OS, Terminator: "\n", /*Quote=*/getDriver().CCPrintOptions);
189 }
190
191 if (LogOnly)
192 return 0;
193
194 std::string Error;
195 bool ExecutionFailed;
196 int Res = C.Execute(Redirects, ErrMsg: &Error, ExecutionFailed: &ExecutionFailed);
197 if (PostCallback)
198 PostCallback(C, Res);
199 if (!Error.empty()) {
200 assert(Res && "Error string set with 0 result code!");
201 getDriver().Diag(DiagID: diag::err_drv_command_failure) << Error;
202 }
203
204 if (Res)
205 FailingCommand = &C;
206
207 return ExecutionFailed ? 1 : Res;
208}
209
210using FailingCommandList = SmallVectorImpl<std::pair<int, const Command *>>;
211
212static bool ActionFailed(const Action *A,
213 const FailingCommandList &FailingCommands) {
214 if (FailingCommands.empty())
215 return false;
216
217 // CUDA/HIP/SYCL can have the same input source code compiled multiple times
218 // so do not compile again if there are already failures. It is OK to abort
219 // the CUDA/HIP/SYCL pipeline on errors.
220 if (A->isOffloading(OKind: Action::OFK_Cuda) || A->isOffloading(OKind: Action::OFK_HIP) ||
221 A->isOffloading(OKind: Action::OFK_SYCL))
222 return true;
223
224 for (const auto &CI : FailingCommands)
225 if (A == &(CI.second->getSource()))
226 return true;
227
228 for (const auto *AI : A->inputs())
229 if (ActionFailed(A: AI, FailingCommands))
230 return true;
231
232 return false;
233}
234
235static bool InputsOk(const Command &C,
236 const FailingCommandList &FailingCommands) {
237 return !ActionFailed(A: &C.getSource(), FailingCommands);
238}
239
240void Compilation::ExecuteJobs(const JobList &Jobs,
241 FailingCommandList &FailingCommands,
242 bool LogOnly) const {
243 // According to UNIX standard, driver need to continue compiling all the
244 // inputs on the command line even one of them failed.
245 // In all but CLMode, execute all the jobs unless the necessary inputs for the
246 // job is missing due to previous failures.
247 for (const auto &Job : Jobs) {
248 if (!InputsOk(C: Job, FailingCommands))
249 continue;
250 const Command *FailingCommand = nullptr;
251 if (int Res = ExecuteCommand(C: Job, FailingCommand, LogOnly)) {
252 FailingCommands.push_back(Elt: std::make_pair(x&: Res, y&: FailingCommand));
253 // Bail as soon as one command fails in cl driver mode.
254 if (TheDriver.IsCLMode())
255 return;
256 }
257 }
258}
259
260void Compilation::initCompilationForDiagnostics() {
261 ForDiagnostics = true;
262
263 // Free actions and jobs.
264 Actions.clear();
265 AllActions.clear();
266 Jobs.clear();
267
268 // Remove temporary files.
269 if (!TheDriver.isSaveTempsEnabled() && !ForceKeepTempFiles)
270 CleanupFileList(Files: TempFiles);
271
272 // Clear temporary/results file lists.
273 TempFiles.clear();
274 ResultFiles.clear();
275 FailureResultFiles.clear();
276
277 // Remove any user specified output. Claim any unclaimed arguments, so as
278 // to avoid emitting warnings about unused args.
279 OptSpecifier OutputOpts[] = {
280 options::OPT_o, options::OPT_MD, options::OPT_MMD, options::OPT_M,
281 options::OPT_MM, options::OPT_MF, options::OPT_MG, options::OPT_MJ,
282 options::OPT_MQ, options::OPT_MT, options::OPT_MV};
283 for (const auto &Opt : OutputOpts) {
284 if (TranslatedArgs->hasArg(Ids: Opt))
285 TranslatedArgs->eraseArg(Id: Opt);
286 }
287 TranslatedArgs->ClaimAllArgs();
288
289 // Force re-creation of the toolchain Args, otherwise our modifications just
290 // above will have no effect.
291 for (auto Arg : TCArgs)
292 if (Arg.second != TranslatedArgs)
293 delete Arg.second;
294 TCArgs.clear();
295
296 // Redirect stdout/stderr to /dev/null.
297 Redirects = {std::nullopt, {""}, {""}};
298
299 // Temporary files added by diagnostics should be kept.
300 ForceKeepTempFiles = true;
301}
302
303StringRef Compilation::getSysRoot() const {
304 return getDriver().SysRoot;
305}
306
307void Compilation::Redirect(ArrayRef<std::optional<StringRef>> Redirects) {
308 this->Redirects = Redirects;
309}
310