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