435 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file implements a stand-alone clang-offload-bundler tool using the11/// 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 55using namespace llvm;56using namespace llvm::object;57using namespace clang;58 59static void PrintVersion(raw_ostream &OS) {60 OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n';61}62 63int 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 for68 // -version and -help) will be hidden.69 cl::OptionCategory70 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 errors153 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 structures170 // Avoid using cl::opt variables after these assignments when possible171 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 else230 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 -inputs242 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 -outputs258 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 we343 // 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 field351 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 if (!checkOffloadBundleID(Target)) {359 return reportError(createStringError(360 errc::invalid_argument,361 "Targets need to follow the format '<offload kind>-<target triple>', "362 "where '<target triple>' follows the format "363 "'<kind>-<arch>-<vendor>-<os>-<env>[-<target id>[:target "364 "features]]'."));365 }366 367 auto OffloadInfo = OffloadTargetInfo(Target, BundlerConfig);368 bool KindIsValid = OffloadInfo.isOffloadKindValid();369 bool TripleIsValid = OffloadInfo.isTripleValid();370 371 StandardizedTargetNames.push_back(OffloadInfo.str());372 373 if (!KindIsValid || !TripleIsValid) {374 SmallVector<char, 128u> Buf;375 raw_svector_ostream Msg(Buf);376 Msg << "invalid target '" << Target << "'";377 if (!KindIsValid)378 Msg << ", unknown offloading kind '" << OffloadInfo.OffloadKind << "'";379 if (!TripleIsValid)380 Msg << ", unknown target triple '" << OffloadInfo.Triple.str() << "'";381 return reportError(createStringError(errc::invalid_argument, Msg.str()));382 }383 384 TargetIDs[OffloadInfo.OffloadKind.str() + "-" + OffloadInfo.Triple.str()]385 .insert(OffloadInfo.TargetID);386 if (KindIsValid && OffloadInfo.hasHostKind()) {387 ++HostTargetNum;388 // Save the index of the input that refers to the host.389 BundlerConfig.HostInputIndex = Index;390 }391 392 if (OffloadInfo.OffloadKind != "hip" && OffloadInfo.OffloadKind != "hipv4")393 HIPOnly = false;394 395 ++Index;396 }397 398 BundlerConfig.TargetNames = StandardizedTargetNames;399 400 for (const auto &TargetID : TargetIDs) {401 if (auto ConflictingTID =402 clang::getConflictTargetIDCombination(TargetID.second)) {403 SmallVector<char, 128u> Buf;404 raw_svector_ostream Msg(Buf);405 Msg << "Cannot bundle inputs with conflicting targets: '"406 << TargetID.first + "-" + ConflictingTID->first << "' and '"407 << TargetID.first + "-" + ConflictingTID->second << "'";408 return reportError(createStringError(errc::invalid_argument, Msg.str()));409 }410 }411 412 // HIP uses clang-offload-bundler to bundle device-only compilation results413 // for multiple GPU archs, therefore allow no host target if all entries414 // are for HIP.415 BundlerConfig.AllowNoHost = HIPOnly;416 417 // Host triple is not really needed for unbundling operation, so do not418 // treat missing host triple as error if we do unbundling.419 if ((Unbundle && HostTargetNum > 1) ||420 (!Unbundle && HostTargetNum != 1 && !BundlerConfig.AllowNoHost)) {421 return reportError(createStringError(422 errc::invalid_argument,423 "expecting exactly one host target but got " + Twine(HostTargetNum)));424 }425 426 OffloadBundler Bundler(BundlerConfig);427 428 return doWork([&]() {429 if (Unbundle)430 return (BundlerConfig.FilesType == "a") ? Bundler.UnbundleArchive()431 : Bundler.UnbundleFiles();432 return Bundler.BundleFiles();433 });434}435