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