1 //===-- clang-offload-bundler/ClangOffloadBundler.cpp ---------------------===//
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 /// \file
10 /// This file implements a stand-alone clang-offload-bundler tool using the
11 /// OffloadBundler API.
12 ///
13 //===----------------------------------------------------------------------===//
14
15 #include "clang/Basic/Cuda.h"
16 #include "clang/Basic/TargetID.h"
17 #include "clang/Basic/Version.h"
18 #include "clang/Driver/OffloadBundler.h"
19 #include "llvm/ADT/ArrayRef.h"
20 #include "llvm/ADT/SmallString.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/ADT/StringMap.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/ADT/Triple.h"
25 #include "llvm/Object/Archive.h"
26 #include "llvm/Object/ArchiveWriter.h"
27 #include "llvm/Object/Binary.h"
28 #include "llvm/Object/ObjectFile.h"
29 #include "llvm/Support/Casting.h"
30 #include "llvm/Support/CommandLine.h"
31 #include "llvm/Support/Debug.h"
32 #include "llvm/Support/Errc.h"
33 #include "llvm/Support/Error.h"
34 #include "llvm/Support/ErrorOr.h"
35 #include "llvm/Support/FileSystem.h"
36 #include "llvm/Support/Host.h"
37 #include "llvm/Support/MemoryBuffer.h"
38 #include "llvm/Support/Path.h"
39 #include "llvm/Support/Program.h"
40 #include "llvm/Support/Signals.h"
41 #include "llvm/Support/StringSaver.h"
42 #include "llvm/Support/WithColor.h"
43 #include "llvm/Support/raw_ostream.h"
44 #include <algorithm>
45 #include <cassert>
46 #include <cstddef>
47 #include <cstdint>
48 #include <forward_list>
49 #include <map>
50 #include <memory>
51 #include <set>
52 #include <string>
53 #include <system_error>
54 #include <utility>
55
56 using namespace llvm;
57 using namespace llvm::object;
58 using namespace clang;
59
PrintVersion(raw_ostream & OS)60 static void PrintVersion(raw_ostream &OS) {
61 OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n';
62 }
63
main(int argc,const char ** argv)64 int main(int argc, const char **argv) {
65
66 cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
67
68 // Mark all our options with this category, everything else (except for
69 // -version and -help) will be hidden.
70 cl::OptionCategory
71 ClangOffloadBundlerCategory("clang-offload-bundler options");
72 cl::list<std::string>
73 InputFileNames("input",
74 cl::desc("Input file."
75 " Can be specified multiple times "
76 "for multiple input files."),
77 cl::cat(ClangOffloadBundlerCategory));
78 cl::list<std::string>
79 InputFileNamesDeprecatedOpt("inputs", cl::CommaSeparated,
80 cl::desc("[<input file>,...] (deprecated)"),
81 cl::cat(ClangOffloadBundlerCategory));
82 cl::list<std::string>
83 OutputFileNames("output",
84 cl::desc("Output file."
85 " Can be specified multiple times "
86 "for multiple output files."),
87 cl::cat(ClangOffloadBundlerCategory));
88 cl::list<std::string>
89 OutputFileNamesDeprecatedOpt("outputs", cl::CommaSeparated,
90 cl::desc("[<output file>,...] (deprecated)"),
91 cl::cat(ClangOffloadBundlerCategory));
92 cl::list<std::string>
93 TargetNames("targets", cl::CommaSeparated,
94 cl::desc("[<offload kind>-<target triple>,...]"),
95 cl::cat(ClangOffloadBundlerCategory));
96 cl::opt<std::string> FilesType(
97 "type", cl::Required,
98 cl::desc("Type of the files to be bundled/unbundled.\n"
99 "Current supported types are:\n"
100 " i - cpp-output\n"
101 " ii - c++-cpp-output\n"
102 " cui - cuda-cpp-output\n"
103 " hipi - hip-cpp-output\n"
104 " d - dependency\n"
105 " ll - llvm\n"
106 " bc - llvm-bc\n"
107 " s - assembler\n"
108 " o - object\n"
109 " a - archive of objects\n"
110 " gch - precompiled-header\n"
111 " ast - clang AST file"),
112 cl::cat(ClangOffloadBundlerCategory));
113 cl::opt<bool>
114 Unbundle("unbundle",
115 cl::desc("Unbundle bundled file into several output files.\n"),
116 cl::init(false), cl::cat(ClangOffloadBundlerCategory));
117 cl::opt<bool>
118 ListBundleIDs("list", cl::desc("List bundle IDs in the bundled file.\n"),
119 cl::init(false), cl::cat(ClangOffloadBundlerCategory));
120 cl::opt<bool> PrintExternalCommands(
121 "###",
122 cl::desc("Print any external commands that are to be executed "
123 "instead of actually executing them - for testing purposes.\n"),
124 cl::init(false), cl::cat(ClangOffloadBundlerCategory));
125 cl::opt<bool>
126 AllowMissingBundles("allow-missing-bundles",
127 cl::desc("Create empty files if bundles are missing "
128 "when unbundling.\n"),
129 cl::init(false), cl::cat(ClangOffloadBundlerCategory));
130 cl::opt<unsigned>
131 BundleAlignment("bundle-align",
132 cl::desc("Alignment of bundle for binary files"),
133 cl::init(1), cl::cat(ClangOffloadBundlerCategory));
134 cl::opt<bool> HipOpenmpCompatible(
135 "hip-openmp-compatible",
136 cl::desc("Treat hip and hipv4 offload kinds as "
137 "compatible with openmp kind, and vice versa.\n"),
138 cl::init(false), cl::cat(ClangOffloadBundlerCategory));
139
140 // Process commandline options and report errors
141 sys::PrintStackTraceOnErrorSignal(argv[0]);
142
143 cl::HideUnrelatedOptions(ClangOffloadBundlerCategory);
144 cl::SetVersionPrinter(PrintVersion);
145 cl::ParseCommandLineOptions(
146 argc, argv,
147 "A tool to bundle several input files of the specified type <type> \n"
148 "referring to the same source file but different targets into a single \n"
149 "one. The resulting file can also be unbundled into different files by \n"
150 "this tool if -unbundle is provided.\n");
151
152 if (Help) {
153 cl::PrintHelpMessage();
154 return 0;
155 }
156
157 /// Class to store bundler options in standard (non-cl::opt) data structures
158 // Avoid using cl::opt variables after these assignments when possible
159 OffloadBundlerConfig BundlerConfig;
160 BundlerConfig.AllowMissingBundles = AllowMissingBundles;
161 BundlerConfig.PrintExternalCommands = PrintExternalCommands;
162 BundlerConfig.HipOpenmpCompatible = HipOpenmpCompatible;
163 BundlerConfig.BundleAlignment = BundleAlignment;
164 BundlerConfig.FilesType = FilesType;
165 BundlerConfig.ObjcopyPath = "";
166
167 BundlerConfig.TargetNames = TargetNames;
168 BundlerConfig.InputFileNames = InputFileNames;
169 BundlerConfig.OutputFileNames = OutputFileNames;
170
171 /// The index of the host input in the list of inputs.
172 BundlerConfig.HostInputIndex = ~0u;
173
174 /// Whether not having host target is allowed.
175 BundlerConfig.AllowNoHost = false;
176
177 auto reportError = [argv](Error E) {
178 logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0]));
179 exit(1);
180 };
181
182 auto doWork = [&](std::function<llvm::Error()> Work) {
183 if (llvm::Error Err = Work()) {
184 reportError(std::move(Err));
185 }
186 };
187
188 auto warningOS = [argv]() -> raw_ostream & {
189 return WithColor::warning(errs(), StringRef(argv[0]));
190 };
191
192 /// Path to the current binary.
193 std::string BundlerExecutable = argv[0];
194
195 if (!llvm::sys::fs::exists(BundlerExecutable))
196 BundlerExecutable =
197 sys::fs::getMainExecutable(argv[0], &BundlerExecutable);
198
199 // Find llvm-objcopy in order to create the bundle binary.
200 ErrorOr<std::string> Objcopy = sys::findProgramByName(
201 "llvm-objcopy",
202 sys::path::parent_path(BundlerExecutable));
203 if (!Objcopy)
204 Objcopy = sys::findProgramByName("llvm-objcopy");
205 if (!Objcopy)
206 reportError(createStringError(Objcopy.getError(),
207 "unable to find 'llvm-objcopy' in path"));
208 else
209 BundlerConfig.ObjcopyPath = *Objcopy;
210
211 if (InputFileNames.getNumOccurrences() != 0 &&
212 InputFileNamesDeprecatedOpt.getNumOccurrences() != 0) {
213 reportError(createStringError(
214 errc::invalid_argument,
215 "-inputs and -input cannot be used together, use only -input instead"));
216 }
217
218 if (InputFileNamesDeprecatedOpt.size()) {
219 warningOS() << "-inputs is deprecated, use -input instead\n";
220 // temporary hack to support -inputs
221 std::vector<std::string> &s = InputFileNames;
222 s.insert(s.end(), InputFileNamesDeprecatedOpt.begin(),
223 InputFileNamesDeprecatedOpt.end());
224 }
225 BundlerConfig.InputFileNames = InputFileNames;
226
227 if (OutputFileNames.getNumOccurrences() != 0 &&
228 OutputFileNamesDeprecatedOpt.getNumOccurrences() != 0) {
229 reportError(createStringError(errc::invalid_argument,
230 "-outputs and -output cannot be used "
231 "together, use only -output instead"));
232 }
233
234 if (OutputFileNamesDeprecatedOpt.size()) {
235 warningOS() << "-outputs is deprecated, use -output instead\n";
236 // temporary hack to support -outputs
237 std::vector<std::string> &s = OutputFileNames;
238 s.insert(s.end(), OutputFileNamesDeprecatedOpt.begin(),
239 OutputFileNamesDeprecatedOpt.end());
240 }
241 BundlerConfig.OutputFileNames = OutputFileNames;
242
243 if (ListBundleIDs) {
244 if (Unbundle) {
245 reportError(
246 createStringError(errc::invalid_argument,
247 "-unbundle and -list cannot be used together"));
248 }
249 if (InputFileNames.size() != 1) {
250 reportError(createStringError(errc::invalid_argument,
251 "only one input file supported for -list"));
252 }
253 if (OutputFileNames.size()) {
254 reportError(createStringError(errc::invalid_argument,
255 "-outputs option is invalid for -list"));
256 }
257 if (TargetNames.size()) {
258 reportError(createStringError(errc::invalid_argument,
259 "-targets option is invalid for -list"));
260 }
261
262 doWork([&]() { return OffloadBundler::ListBundleIDsInFile(
263 InputFileNames.front(),
264 BundlerConfig); });
265 return 0;
266 }
267
268 if (OutputFileNames.size() == 0) {
269 reportError(
270 createStringError(errc::invalid_argument, "no output file specified!"));
271 }
272
273 if (TargetNames.getNumOccurrences() == 0) {
274 reportError(createStringError(
275 errc::invalid_argument,
276 "for the --targets option: must be specified at least once!"));
277 }
278
279 if (Unbundle) {
280 if (InputFileNames.size() != 1) {
281 reportError(createStringError(
282 errc::invalid_argument,
283 "only one input file supported in unbundling mode"));
284 }
285 if (OutputFileNames.size() != TargetNames.size()) {
286 reportError(createStringError(errc::invalid_argument,
287 "number of output files and targets should "
288 "match in unbundling mode"));
289 }
290 } else {
291 if (BundlerConfig.FilesType == "a") {
292 reportError(createStringError(errc::invalid_argument,
293 "Archive files are only supported "
294 "for unbundling"));
295 }
296 if (OutputFileNames.size() != 1) {
297 reportError(createStringError(
298 errc::invalid_argument,
299 "only one output file supported in bundling mode"));
300 }
301 if (InputFileNames.size() != TargetNames.size()) {
302 reportError(createStringError(
303 errc::invalid_argument,
304 "number of input files and targets should match in bundling mode"));
305 }
306 }
307
308 // Verify that the offload kinds and triples are known. We also check that we
309 // have exactly one host target.
310 unsigned Index = 0u;
311 unsigned HostTargetNum = 0u;
312 bool HIPOnly = true;
313 llvm::DenseSet<StringRef> ParsedTargets;
314 // Map {offload-kind}-{triple} to target IDs.
315 std::map<std::string, std::set<StringRef>> TargetIDs;
316 for (StringRef Target : TargetNames) {
317 if (ParsedTargets.contains(Target)) {
318 reportError(createStringError(errc::invalid_argument,
319 "Duplicate targets are not allowed"));
320 }
321 ParsedTargets.insert(Target);
322
323 auto OffloadInfo = OffloadTargetInfo(Target, BundlerConfig);
324 bool KindIsValid = OffloadInfo.isOffloadKindValid();
325 bool TripleIsValid = OffloadInfo.isTripleValid();
326
327 if (!KindIsValid || !TripleIsValid) {
328 SmallVector<char, 128u> Buf;
329 raw_svector_ostream Msg(Buf);
330 Msg << "invalid target '" << Target << "'";
331 if (!KindIsValid)
332 Msg << ", unknown offloading kind '" << OffloadInfo.OffloadKind << "'";
333 if (!TripleIsValid)
334 Msg << ", unknown target triple '" << OffloadInfo.Triple.str() << "'";
335 reportError(createStringError(errc::invalid_argument, Msg.str()));
336 }
337
338 TargetIDs[OffloadInfo.OffloadKind.str() + "-" + OffloadInfo.Triple.str()]
339 .insert(OffloadInfo.TargetID);
340 if (KindIsValid && OffloadInfo.hasHostKind()) {
341 ++HostTargetNum;
342 // Save the index of the input that refers to the host.
343 BundlerConfig.HostInputIndex = Index;
344 }
345
346 if (OffloadInfo.OffloadKind != "hip" && OffloadInfo.OffloadKind != "hipv4")
347 HIPOnly = false;
348
349 ++Index;
350 }
351 for (const auto &TargetID : TargetIDs) {
352 if (auto ConflictingTID =
353 clang::getConflictTargetIDCombination(TargetID.second)) {
354 SmallVector<char, 128u> Buf;
355 raw_svector_ostream Msg(Buf);
356 Msg << "Cannot bundle inputs with conflicting targets: '"
357 << TargetID.first + "-" + ConflictingTID->first << "' and '"
358 << TargetID.first + "-" + ConflictingTID->second << "'";
359 reportError(createStringError(errc::invalid_argument, Msg.str()));
360 }
361 }
362
363 // HIP uses clang-offload-bundler to bundle device-only compilation results
364 // for multiple GPU archs, therefore allow no host target if all entries
365 // are for HIP.
366 BundlerConfig.AllowNoHost = HIPOnly;
367
368 // Host triple is not really needed for unbundling operation, so do not
369 // treat missing host triple as error if we do unbundling.
370 if ((Unbundle && HostTargetNum > 1) ||
371 (!Unbundle && HostTargetNum != 1 && !BundlerConfig.AllowNoHost)) {
372 reportError(createStringError(errc::invalid_argument,
373 "expecting exactly one host target but got " +
374 Twine(HostTargetNum)));
375 }
376
377 OffloadBundler Bundler(BundlerConfig);
378
379 doWork([&]() {
380 if (Unbundle) {
381 if (BundlerConfig.FilesType == "a")
382 return Bundler.UnbundleArchive();
383 else
384 return Bundler.UnbundleFiles();
385 } else
386 return Bundler.BundleFiles();
387 });
388 return 0;
389 }
390