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 60 static void PrintVersion(raw_ostream &OS) { 61 OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n'; 62 } 63 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