xref: /openbsd-src/gnu/llvm/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
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