ClangOffloadBundler.cpp 14 KB

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