brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.6 KiB · 75de970 Raw
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