diff --git a/clang/include/clang/Driver/OffloadBundler.h b/clang/include/clang/Driver/OffloadBundler.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Driver/OffloadBundler.h @@ -0,0 +1,89 @@ +//===- OffloadBundler.h - File Bundling and Unbundling ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines an offload bundling API that bundles different files +/// that relate with the same source code but different targets into a single +/// one. Also the implements the opposite functionality, i.e. unbundle files +/// previous created by this API. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_DRIVER_OFFLOADBUNDLER_H +#define LLVM_CLANG_DRIVER_OFFLOADBUNDLER_H + +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace clang { + +class OffloadBundlerConfig { +public: + bool AllowNoHost = false; + bool AllowMissingBundles = false; + bool CheckInputArchive = false; + bool PrintExternalCommands = false; + bool HipOpenmpCompatible = false; + + unsigned BundleAlignment = 1; + unsigned HostInputIndex = ~0u; + + std::string FilesType; + std::string ObjcopyPath; + + // TODO: Convert these to llvm::SmallVector + std::vector TargetNames; + std::vector InputFileNames; + std::vector OutputFileNames; +}; + +class OffloadBundler { +public: + const OffloadBundlerConfig &BundlerConfig; + + // TODO: Add error checking from ClangOffloadBundler.cpp + OffloadBundler(const OffloadBundlerConfig &BC) : BundlerConfig(BC) {} + + // List bundle IDs. Return true if an error was found. + static llvm::Error + ListBundleIDsInFile(llvm::StringRef InputFileName, + const OffloadBundlerConfig &BundlerConfig); + + llvm::Error BundleFiles(); + llvm::Error UnbundleFiles(); + llvm::Error UnbundleArchive(); +}; + +/// Obtain the offload kind, real machine triple, and an optional GPUArch +/// out of the target information specified by the user. +/// Bundle Entry ID (or, Offload Target String) has following components: +/// * Offload Kind - Host, OpenMP, or HIP +/// * Triple - Standard LLVM Triple +/// * GPUArch (Optional) - Processor name, like gfx906 or sm_30 +struct OffloadTargetInfo { + llvm::StringRef OffloadKind; + llvm::Triple Triple; + llvm::StringRef GPUArch; + + const OffloadBundlerConfig &BundlerConfig; + + OffloadTargetInfo(const llvm::StringRef Target, + const OffloadBundlerConfig &BC); + bool hasHostKind() const; + bool isOffloadKindValid() const; + bool isOffloadKindCompatible(const llvm::StringRef TargetOffloadKind) const; + bool isTripleValid() const; + bool operator==(const OffloadTargetInfo &Target) const; + std::string str(); +}; + +} // namespace clang + +#endif // LLVM_CLANG_DRIVER_OFFLOADBUNDLER_H diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt --- a/clang/lib/Driver/CMakeLists.txt +++ b/clang/lib/Driver/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS BinaryFormat MC + Object Option ProfileData Support @@ -20,6 +21,7 @@ DriverOptions.cpp Job.cpp Multilib.cpp + OffloadBundler.cpp OptionUtils.cpp Phases.cpp SanitizerArgs.cpp diff --git a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp copy from clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp copy to clang/lib/Driver/OffloadBundler.cpp --- a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -1,4 +1,4 @@ -//===-- clang-offload-bundler/ClangOffloadBundler.cpp ---------------------===// +//===- OffloadBundler.cpp - File Bundling and Unbundling ------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,15 +7,16 @@ //===----------------------------------------------------------------------===// /// /// \file -/// This file implements a clang-offload-bundler that bundles different -/// files that relate with the same source code but different targets into a -/// single one. Also the implements the opposite functionality, i.e. unbundle -/// files previous created by this tool. +/// This file implements an offload bundling API that bundles different files +/// that relate with the same source code but different targets into a single +/// one. Also the implements the opposite functionality, i.e. unbundle files +/// previous created by this API. /// //===----------------------------------------------------------------------===// #include "clang/Basic/Cuda.h" #include "clang/Basic/Version.h" +#include "clang/Driver/OffloadBundler.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -55,162 +56,71 @@ using namespace llvm; using namespace llvm::object; - -static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); - -// Mark all our options with this category, everything else (except for -version -// and -help) will be hidden. -static cl::OptionCategory - ClangOffloadBundlerCategory("clang-offload-bundler options"); -static cl::list - InputFileNames("input", - cl::desc("Input file." - " Can be specified multiple times " - "for multiple input files."), - cl::cat(ClangOffloadBundlerCategory)); -static cl::list - InputFileNamesDeprecatedOpt("inputs", cl::CommaSeparated, - cl::desc("[,...] (deprecated)"), - cl::cat(ClangOffloadBundlerCategory)); -static cl::list - OutputFileNames("output", - cl::desc("Output file." - " Can be specified multiple times " - "for multiple output files."), - cl::cat(ClangOffloadBundlerCategory)); -static cl::list - OutputFileNamesDeprecatedOpt("outputs", cl::CommaSeparated, - cl::desc("[,...] (deprecated)"), - cl::cat(ClangOffloadBundlerCategory)); -static cl::list - TargetNames("targets", cl::CommaSeparated, - cl::desc("[-,...]"), - cl::cat(ClangOffloadBundlerCategory)); -static cl::opt - FilesType("type", cl::Required, - cl::desc("Type of the files to be bundled/unbundled.\n" - "Current supported types are:\n" - " i - cpp-output\n" - " ii - c++-cpp-output\n" - " cui - cuda/hip-output\n" - " d - dependency\n" - " ll - llvm\n" - " bc - llvm-bc\n" - " s - assembler\n" - " o - object\n" - " a - archive of objects\n" - " gch - precompiled-header\n" - " ast - clang AST file"), - cl::cat(ClangOffloadBundlerCategory)); -static cl::opt - Unbundle("unbundle", - cl::desc("Unbundle bundled file into several output files.\n"), - cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt - ListBundleIDs("list", cl::desc("List bundle IDs in the bundled file.\n"), - cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt PrintExternalCommands( - "###", - cl::desc("Print any external commands that are to be executed " - "instead of actually executing them - for testing purposes.\n"), - cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt - AllowMissingBundles("allow-missing-bundles", - cl::desc("Create empty files if bundles are missing " - "when unbundling.\n"), - cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt - BundleAlignment("bundle-align", - cl::desc("Alignment of bundle for binary files"), - cl::init(1), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt HipOpenmpCompatible( - "hip-openmp-compatible", - cl::desc("Treat hip and hipv4 offload kinds as " - "compatible with openmp kind, and vice versa.\n"), - cl::init(false), cl::cat(ClangOffloadBundlerCategory)); +using namespace clang; /// Magic string that marks the existence of offloading data. #define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" -/// The index of the host input in the list of inputs. -static unsigned HostInputIndex = ~0u; - -/// Whether not having host target is allowed. -static bool AllowNoHost = false; - -/// Path to the current binary. -static std::string BundlerExecutable; - -/// Obtain the offload kind, real machine triple, and an optional GPUArch -/// out of the target information specified by the user. -/// Bundle Entry ID (or, Offload Target String) has following components: -/// * Offload Kind - Host, OpenMP, or HIP -/// * Triple - Standard LLVM Triple -/// * GPUArch (Optional) - Processor name, like gfx906 or sm_30 - -struct OffloadTargetInfo { - StringRef OffloadKind; - llvm::Triple Triple; - StringRef GPUArch; - - OffloadTargetInfo(const StringRef Target) { - auto TargetFeatures = Target.split(':'); - auto TripleOrGPU = TargetFeatures.first.rsplit('-'); - - if (clang::StringToCudaArch(TripleOrGPU.second) != - clang::CudaArch::UNKNOWN) { - auto KindTriple = TripleOrGPU.first.split('-'); - this->OffloadKind = KindTriple.first; - this->Triple = llvm::Triple(KindTriple.second); - this->GPUArch = Target.substr(Target.find(TripleOrGPU.second)); - } else { - auto KindTriple = TargetFeatures.first.split('-'); - this->OffloadKind = KindTriple.first; - this->Triple = llvm::Triple(KindTriple.second); - this->GPUArch = ""; - } - } +OffloadTargetInfo::OffloadTargetInfo(const StringRef Target, + const OffloadBundlerConfig &BC) + : BundlerConfig(BC) { - bool hasHostKind() const { return this->OffloadKind == "host"; } + // TODO: Add error checking from ClangOffloadBundler.cpp + auto TargetFeatures = Target.split(':'); + auto TripleOrGPU = TargetFeatures.first.rsplit('-'); - bool isOffloadKindValid() const { - return OffloadKind == "host" || OffloadKind == "openmp" || - OffloadKind == "hip" || OffloadKind == "hipv4"; + if (clang::StringToCudaArch(TripleOrGPU.second) != + clang::CudaArch::UNKNOWN) { + auto KindTriple = TripleOrGPU.first.split('-'); + this->OffloadKind = KindTriple.first; + this->Triple = llvm::Triple(KindTriple.second); + this->GPUArch = Target.substr(Target.find(TripleOrGPU.second)); + } else { + auto KindTriple = TargetFeatures.first.split('-'); + this->OffloadKind = KindTriple.first; + this->Triple = llvm::Triple(KindTriple.second); + this->GPUArch = ""; } +} - bool isOffloadKindCompatible(const StringRef TargetOffloadKind) const { - if (OffloadKind == TargetOffloadKind) - return true; - if (HipOpenmpCompatible) { - bool HIPCompatibleWithOpenMP = - OffloadKind.startswith_insensitive("hip") && - TargetOffloadKind == "openmp"; - bool OpenMPCompatibleWithHIP = - OffloadKind == "openmp" && - TargetOffloadKind.startswith_insensitive("hip"); - return HIPCompatibleWithOpenMP || OpenMPCompatibleWithHIP; - } - return false; - } +bool OffloadTargetInfo::hasHostKind() const { + return this->OffloadKind == "host"; +} - bool isTripleValid() const { - return !Triple.str().empty() && Triple.getArch() != Triple::UnknownArch; - } +bool OffloadTargetInfo::isOffloadKindValid() const { + return OffloadKind == "host" || OffloadKind == "openmp" || + OffloadKind == "hip" || OffloadKind == "hipv4"; +} - bool operator==(const OffloadTargetInfo &Target) const { - return OffloadKind == Target.OffloadKind && - Triple.isCompatibleWith(Target.Triple) && GPUArch == Target.GPUArch; +bool OffloadTargetInfo::isOffloadKindCompatible( + const StringRef TargetOffloadKind) const { + if (OffloadKind == TargetOffloadKind) + return true; + if (BundlerConfig.HipOpenmpCompatible) { + bool HIPCompatibleWithOpenMP = + OffloadKind.startswith_insensitive("hip") && + TargetOffloadKind == "openmp"; + bool OpenMPCompatibleWithHIP = + OffloadKind == "openmp" && + TargetOffloadKind.startswith_insensitive("hip"); + return HIPCompatibleWithOpenMP || OpenMPCompatibleWithHIP; } + return false; +} - std::string str() { - return Twine(OffloadKind + "-" + Triple.str() + "-" + GPUArch).str(); - } -}; +bool OffloadTargetInfo::isTripleValid() const { + return !Triple.str().empty() && Triple.getArch() != Triple::UnknownArch; +} + +bool OffloadTargetInfo::operator==(const OffloadTargetInfo &Target) const { + return OffloadKind == Target.OffloadKind && + Triple.isCompatibleWith(Target.Triple) && + GPUArch == Target.GPUArch; +} + +std::string OffloadTargetInfo::str() { + return Twine(OffloadKind + "-" + Triple.str() + "-" + GPUArch).str(); +} static StringRef getDeviceFileExtension(StringRef Device, StringRef BundleFileName) { @@ -280,7 +190,6 @@ virtual Error listBundleIDs(MemoryBuffer &Input) { if (Error Err = ReadHeader(Input)) return Err; - return forEachBundle(Input, [&](const BundleInfo &Info) -> Error { llvm::outs() << Info.BundleID << '\n'; Error Err = listBundleIDsCallback(Input, Info); @@ -387,8 +296,12 @@ /// Current bundle target to be written. std::string CurWriteBundleTarget; + /// Configuration options and arrays for this bundler job + const OffloadBundlerConfig &BundlerConfig; + public: - BinaryFileHandler() {} + // TODO: Add error checking from ClangOffloadBundler.cpp + BinaryFileHandler(const OffloadBundlerConfig &BC) : BundlerConfig(BC) {} ~BinaryFileHandler() final {} @@ -482,13 +395,14 @@ Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) final { + // Compute size of the header. uint64_t HeaderSize = 0; HeaderSize += sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; HeaderSize += 8; // Number of Bundles - for (auto &T : TargetNames) { + for (auto &T : BundlerConfig.TargetNames) { HeaderSize += 3 * 8; // Bundle offset, Size of bundle and size of triple. HeaderSize += T.size(); // The triple. } @@ -496,12 +410,12 @@ // Write to the buffer the header. OS << OFFLOAD_BUNDLER_MAGIC_STR; - Write8byteIntegerToBuffer(OS, TargetNames.size()); + Write8byteIntegerToBuffer(OS, BundlerConfig.TargetNames.size()); unsigned Idx = 0; - for (auto &T : TargetNames) { + for (auto &T : BundlerConfig.TargetNames) { MemoryBuffer &MB = *Inputs[Idx++]; - HeaderSize = alignTo(HeaderSize, BundleAlignment); + HeaderSize = alignTo(HeaderSize, BundlerConfig.BundleAlignment); // Bundle offset. Write8byteIntegerToBuffer(OS, HeaderSize); // Size of the bundle (adds to the next bundle's offset) @@ -606,10 +520,15 @@ section_iterator CurrentSection; section_iterator NextSection; + /// Configuration options and arrays for this bundler job + const OffloadBundlerConfig &BundlerConfig; + public: - ObjectFileHandler(std::unique_ptr ObjIn) + // TODO: Add error checking from ClangOffloadBundler.cpp + ObjectFileHandler(std::unique_ptr ObjIn, + const OffloadBundlerConfig &BC) : Obj(std::move(ObjIn)), CurrentSection(Obj->section_begin()), - NextSection(Obj->section_begin()) {} + NextSection(Obj->section_begin()), BundlerConfig(BC) {} ~ObjectFileHandler() final {} @@ -650,7 +569,8 @@ Error WriteHeader(raw_fd_ostream &OS, ArrayRef> Inputs) final { - assert(HostInputIndex != ~0u && "Host input index not defined."); + assert(BundlerConfig.HostInputIndex != ~0u && + "Host input index not defined."); // Record number of inputs. NumberOfInputs = Inputs.size(); @@ -665,7 +585,8 @@ Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { assert(NumberOfProcessedInputs <= NumberOfInputs && "Processing more inputs that actually exist!"); - assert(HostInputIndex != ~0u && "Host input index not defined."); + assert(BundlerConfig.HostInputIndex != ~0u && + "Host input index not defined."); // If this is not the last output, we don't have to do anything. if (NumberOfProcessedInputs != NumberOfInputs) @@ -676,14 +597,8 @@ // link editor to remove them from linker inputs when linking executable or // shared library. - // Find llvm-objcopy in order to create the bundle binary. - ErrorOr Objcopy = sys::findProgramByName( - "llvm-objcopy", sys::path::parent_path(BundlerExecutable)); - if (!Objcopy) - Objcopy = sys::findProgramByName("llvm-objcopy"); - if (!Objcopy) - return createStringError(Objcopy.getError(), - "unable to find 'llvm-objcopy' in path"); + assert(BundlerConfig.ObjcopyPath != "" && + "llvm-objcopy path not specified"); // We write to the output file directly. So, we close it and use the name // to pass down to llvm-objcopy. @@ -697,9 +612,10 @@ BumpPtrAllocator Alloc; StringSaver SS{Alloc}; SmallVector ObjcopyArgs{"llvm-objcopy"}; + for (unsigned I = 0; I < NumberOfInputs; ++I) { - StringRef InputFile = InputFileNames[I]; - if (I == HostInputIndex) { + StringRef InputFile = BundlerConfig.InputFileNames[I]; + if (I == BundlerConfig.HostInputIndex) { // Special handling for the host bundle. We do not need to add a // standard bundle for the host object since we are going to use fat // object as a host object. Therefore use dummy contents (one zero byte) @@ -711,17 +627,20 @@ } ObjcopyArgs.push_back(SS.save(Twine("--add-section=") + - OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + + OFFLOAD_BUNDLER_MAGIC_STR + + BundlerConfig.TargetNames[I] + "=" + InputFile)); ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + - OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + + OFFLOAD_BUNDLER_MAGIC_STR + + BundlerConfig.TargetNames[I] + "=readonly,exclude")); } ObjcopyArgs.push_back("--"); - ObjcopyArgs.push_back(InputFileNames[HostInputIndex]); - ObjcopyArgs.push_back(OutputFileNames.front()); + ObjcopyArgs.push_back( + BundlerConfig.InputFileNames[BundlerConfig.HostInputIndex]); + ObjcopyArgs.push_back(BundlerConfig.OutputFileNames.front()); - if (Error Err = executeObjcopy(*Objcopy, ObjcopyArgs)) + if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs)) return Err; return Error::success(); @@ -732,10 +651,10 @@ } private: - static Error executeObjcopy(StringRef Objcopy, ArrayRef Args) { + Error executeObjcopy(StringRef Objcopy, ArrayRef Args) { // If the user asked for the commands to be printed out, we do that // instead of executing it. - if (PrintExternalCommands) { + if (BundlerConfig.PrintExternalCommands) { errs() << "\"" << Objcopy << "\""; for (StringRef Arg : drop_begin(Args, 1)) errs() << " \"" << Arg << "\""; @@ -868,24 +787,29 @@ /// handler if we know how to deal with that format, otherwise we use a default /// binary file handler. static std::unique_ptr -CreateObjectFileHandler(MemoryBuffer &FirstInput) { +CreateObjectFileHandler(MemoryBuffer &FirstInput, + const OffloadBundlerConfig &BundlerConfig) { // Check if the input file format is one that we know how to deal with. Expected> BinaryOrErr = createBinary(FirstInput); // We only support regular object files. If failed to open the input as a // known binary or this is not an object file use the default binary handler. if (errorToBool(BinaryOrErr.takeError()) || !isa(*BinaryOrErr)) - return std::make_unique(); + return std::make_unique(BundlerConfig); // Otherwise create an object file handler. The handler will be owned by the // client of this function. return std::make_unique( - std::unique_ptr(cast(BinaryOrErr->release()))); + std::unique_ptr(cast(BinaryOrErr->release())), + BundlerConfig); } /// Return an appropriate handler given the input files and options. static Expected> -CreateFileHandler(MemoryBuffer &FirstInput) { +CreateFileHandler(MemoryBuffer &FirstInput, + const OffloadBundlerConfig &BundlerConfig) { + std::string FilesType = BundlerConfig.FilesType; + if (FilesType == "i") return std::make_unique(/*Comment=*/"//"); if (FilesType == "ii") @@ -899,35 +823,58 @@ if (FilesType == "ll") return std::make_unique(/*Comment=*/";"); if (FilesType == "bc") - return std::make_unique(); + return std::make_unique(BundlerConfig); if (FilesType == "s") return std::make_unique(/*Comment=*/"#"); if (FilesType == "o") - return CreateObjectFileHandler(FirstInput); + return CreateObjectFileHandler(FirstInput, BundlerConfig); if (FilesType == "a") - return CreateObjectFileHandler(FirstInput); + return CreateObjectFileHandler(FirstInput, BundlerConfig); if (FilesType == "gch") - return std::make_unique(); + return std::make_unique(BundlerConfig); if (FilesType == "ast") - return std::make_unique(); + return std::make_unique(BundlerConfig); return createStringError(errc::invalid_argument, "'" + FilesType + "': invalid file type specified"); } +// List bundle IDs. Return true if an error was found. +Error OffloadBundler::ListBundleIDsInFile(StringRef InputFileName, + const OffloadBundlerConfig &BundlerConfig) { + // Open Input file. + ErrorOr> CodeOrErr = + MemoryBuffer::getFileOrSTDIN(InputFileName); + if (std::error_code EC = CodeOrErr.getError()) + return createFileError(InputFileName, EC); + + MemoryBuffer &Input = **CodeOrErr; + + // Select the right files handler. + Expected> FileHandlerOrErr = + CreateFileHandler(Input, BundlerConfig); + if (!FileHandlerOrErr) + return FileHandlerOrErr.takeError(); + + std::unique_ptr &FH = *FileHandlerOrErr; + assert(FH); + return FH->listBundleIDs(Input); +} + /// Bundle the files. Return true if an error was found. -static Error BundleFiles() { +Error OffloadBundler::BundleFiles() { std::error_code EC; // Create output file. - raw_fd_ostream OutputFile(OutputFileNames.front(), EC, sys::fs::OF_None); + raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), + EC, sys::fs::OF_None); if (EC) - return createFileError(OutputFileNames.front(), EC); + return createFileError(BundlerConfig.OutputFileNames.front(), EC); // Open input files. SmallVector, 8u> InputBuffers; - InputBuffers.reserve(InputFileNames.size()); - for (auto &I : InputFileNames) { + InputBuffers.reserve(BundlerConfig.InputFileNames.size()); + for (auto &I : BundlerConfig.InputFileNames) { ErrorOr> CodeOrErr = MemoryBuffer::getFileOrSTDIN(I); if (std::error_code EC = CodeOrErr.getError()) @@ -936,10 +883,12 @@ } // Get the file handler. We use the host buffer as reference. - assert((HostInputIndex != ~0u || AllowNoHost) && + assert((BundlerConfig.HostInputIndex != ~0u || BundlerConfig.AllowNoHost) && "Host input index undefined??"); Expected> FileHandlerOrErr = - CreateFileHandler(*InputBuffers[AllowNoHost ? 0 : HostInputIndex]); + CreateFileHandler(*InputBuffers[BundlerConfig.AllowNoHost ? 0 + : BundlerConfig.HostInputIndex], + BundlerConfig); if (!FileHandlerOrErr) return FileHandlerOrErr.takeError(); @@ -953,7 +902,7 @@ // Write all bundles along with the start/end markers. If an error was found // writing the end of the bundle component, abort the bundle writing. auto Input = InputBuffers.begin(); - for (auto &Triple : TargetNames) { + for (auto &Triple : BundlerConfig.TargetNames) { if (Error Err = FH->WriteBundleStart(OutputFile, Triple)) return Err; if (Error Err = FH->WriteBundle(OutputFile, **Input)) @@ -965,40 +914,19 @@ return Error::success(); } -// List bundle IDs. Return true if an error was found. -static Error ListBundleIDsInFile(StringRef InputFileName) { - // Open Input file. - ErrorOr> CodeOrErr = - MemoryBuffer::getFileOrSTDIN(InputFileName); - if (std::error_code EC = CodeOrErr.getError()) - return createFileError(InputFileName, EC); - - MemoryBuffer &Input = **CodeOrErr; - - // Select the right files handler. - Expected> FileHandlerOrErr = - CreateFileHandler(Input); - if (!FileHandlerOrErr) - return FileHandlerOrErr.takeError(); - - std::unique_ptr &FH = *FileHandlerOrErr; - assert(FH); - return FH->listBundleIDs(Input); -} - // Unbundle the files. Return true if an error was found. -static Error UnbundleFiles() { +Error OffloadBundler::UnbundleFiles() { // Open Input file. ErrorOr> CodeOrErr = - MemoryBuffer::getFileOrSTDIN(InputFileNames.front()); + MemoryBuffer::getFileOrSTDIN(BundlerConfig.InputFileNames.front()); if (std::error_code EC = CodeOrErr.getError()) - return createFileError(InputFileNames.front(), EC); + return createFileError(BundlerConfig.InputFileNames.front(), EC); MemoryBuffer &Input = **CodeOrErr; // Select the right files handler. Expected> FileHandlerOrErr = - CreateFileHandler(Input); + CreateFileHandler(Input, BundlerConfig); if (!FileHandlerOrErr) return FileHandlerOrErr.takeError(); @@ -1011,8 +939,8 @@ // Create a work list that consist of the map triple/output file. StringMap Worklist; - auto Output = OutputFileNames.begin(); - for (auto &Triple : TargetNames) { + auto Output = BundlerConfig.OutputFileNames.begin(); + for (auto &Triple : BundlerConfig.TargetNames) { Worklist[Triple] = *Output; ++Output; } @@ -1050,12 +978,12 @@ Worklist.erase(Output); // Record if we found the host bundle. - auto OffloadInfo = OffloadTargetInfo(CurTriple); + auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig); if (OffloadInfo.hasHostKind()) FoundHostBundle = true; } - if (!AllowMissingBundles && !Worklist.empty()) { + if (!BundlerConfig.AllowMissingBundles && !Worklist.empty()) { std::string ErrMsg = "Can't find bundles for"; std::set Sorted; for (auto &E : Worklist) @@ -1076,7 +1004,7 @@ // If no bundles were found, assume the input file is the host bundle and // create empty files for the remaining targets. - if (Worklist.size() == TargetNames.size()) { + if (Worklist.size() == BundlerConfig.TargetNames.size()) { for (auto &E : Worklist) { std::error_code EC; raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); @@ -1084,7 +1012,7 @@ return createFileError(E.second, EC); // If this entry has a host kind, copy the input file to the output file. - auto OffloadInfo = OffloadTargetInfo(E.getKey()); + auto OffloadInfo = OffloadTargetInfo(E.getKey(), BundlerConfig); if (OffloadInfo.hasHostKind()) OutputFile.write(Input.getBufferStart(), Input.getBufferSize()); } @@ -1093,7 +1021,7 @@ // If we found elements, we emit an error if none of those were for the host // in case host bundle name was provided in command line. - if (!FoundHostBundle && HostInputIndex != ~0u) + if (!FoundHostBundle && BundlerConfig.HostInputIndex != ~0u) return createStringError(inconvertibleErrorCode(), "Can't find bundle for the host target"); @@ -1158,20 +1086,21 @@ /// @brief Computes a list of targets among all given targets which are /// compatible with this code object -/// @param [in] CodeObjectInfo Code Object -/// @param [out] CompatibleTargets List of all compatible targets among all +/// @param [in] Code Object \p CodeObject +/// @param [out] List of all compatible targets \p CompatibleTargets among all /// given targets /// @return false, if no compatible target is found. static bool getCompatibleOffloadTargets(OffloadTargetInfo &CodeObjectInfo, - SmallVectorImpl &CompatibleTargets) { + SmallVectorImpl &CompatibleTargets, + const OffloadBundlerConfig &BundlerConfig) { if (!CompatibleTargets.empty()) { DEBUG_WITH_TYPE("CodeObjectCompatibility", dbgs() << "CompatibleTargets list should be empty\n"); return false; } - for (auto &Target : TargetNames) { - auto TargetInfo = OffloadTargetInfo(Target); + for (auto &Target : BundlerConfig.TargetNames) { + auto TargetInfo = OffloadTargetInfo(Target, BundlerConfig); if (isCodeObjectCompatible(CodeObjectInfo, TargetInfo)) CompatibleTargets.push_back(Target); } @@ -1185,7 +1114,7 @@ /// particular offload target. The created archive file does not /// contain an index of the symbols and code object files are named as /// <->, with ':' replaced with '_'. -static Error UnbundleArchive() { +Error OffloadBundler::UnbundleArchive() { std::vector> ArchiveBuffers; /// Map of target names with list of object files that will form the device @@ -1195,17 +1124,18 @@ // Map of target names and output archive filenames StringMap TargetOutputFileNameMap; - auto Output = OutputFileNames.begin(); - for (auto &Target : TargetNames) { + auto Output = BundlerConfig.OutputFileNames.begin(); + for (auto &Target : BundlerConfig.TargetNames) { TargetOutputFileNameMap[Target] = *Output; ++Output; } - StringRef IFName = InputFileNames.front(); + StringRef IFName = BundlerConfig.InputFileNames.front(); + ErrorOr> BufOrErr = MemoryBuffer::getFileOrSTDIN(IFName, true, false); if (std::error_code EC = BufOrErr.getError()) - return createFileError(InputFileNames.front(), EC); + return createFileError(BundlerConfig.InputFileNames.front(), EC); ArchiveBuffers.push_back(std::move(*BufOrErr)); Expected> LibOrErr = @@ -1237,7 +1167,7 @@ MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false); Expected> FileHandlerOrErr = - CreateFileHandler(*CodeObjectBuffer); + CreateFileHandler(*CodeObjectBuffer, BundlerConfig); if (!FileHandlerOrErr) return FileHandlerOrErr.takeError(); @@ -1255,7 +1185,7 @@ Optional OptionalCurBundleID = *CurBundleIDOrErr; // No device code in this child, skip. - if (!OptionalCurBundleID) + if (!OptionalCurBundleID.hasValue()) continue; StringRef CodeObject = *OptionalCurBundleID; @@ -1263,11 +1193,12 @@ // archive. while (!CodeObject.empty()) { SmallVector CompatibleTargets; - auto CodeObjectInfo = OffloadTargetInfo(CodeObject); + auto CodeObjectInfo = OffloadTargetInfo(CodeObject, BundlerConfig); if (CodeObjectInfo.hasHostKind()) { // Do nothing, we don't extract host code yet. } else if (getCompatibleOffloadTargets(CodeObjectInfo, - CompatibleTargets)) { + CompatibleTargets, + BundlerConfig)) { std::string BundleData; raw_string_ostream DataStream(BundleData); if (Error Err = @@ -1318,14 +1249,14 @@ if (!NextTripleOrErr) return NextTripleOrErr.takeError(); - CodeObject = NextTripleOrErr->value_or(""); + CodeObject = ((*NextTripleOrErr).hasValue()) ? **NextTripleOrErr : ""; } // End of processing of all bundle entries of this child of input archive. } // End of while over children of input archive. assert(!ArchiveErr && "Error occurred while reading archive!"); /// Write out an archive for each target - for (auto &Target : TargetNames) { + for (auto &Target : BundlerConfig.TargetNames) { StringRef FileName = TargetOutputFileNameMap[Target]; StringMapIterator> CurArchiveMembers = OutputArchivesMap.find(Target); @@ -1334,7 +1265,7 @@ true, getDefaultArchiveKindForHost(), true, false, nullptr)) return WriteErr; - } else if (!AllowMissingBundles) { + } else if (!BundlerConfig.AllowMissingBundles) { std::string ErrMsg = Twine("no compatible code object found for the target '" + Target + "' in heterogeneous archive library: " + IFName) @@ -1355,201 +1286,3 @@ return Error::success(); } - -static void PrintVersion(raw_ostream &OS) { - OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n'; -} - -int main(int argc, const char **argv) { - sys::PrintStackTraceOnErrorSignal(argv[0]); - - cl::HideUnrelatedOptions(ClangOffloadBundlerCategory); - cl::SetVersionPrinter(PrintVersion); - cl::ParseCommandLineOptions( - argc, argv, - "A tool to bundle several input files of the specified type \n" - "referring to the same source file but different targets into a single \n" - "one. The resulting file can also be unbundled into different files by \n" - "this tool if -unbundle is provided.\n"); - - if (Help) { - cl::PrintHelpMessage(); - return 0; - } - - auto reportError = [argv](Error E) { - logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0])); - exit(1); - }; - - auto doWork = [&](std::function Work) { - // Save the current executable directory as it will be useful to find other - // tools. - BundlerExecutable = argv[0]; - if (!llvm::sys::fs::exists(BundlerExecutable)) - BundlerExecutable = - sys::fs::getMainExecutable(argv[0], &BundlerExecutable); - - if (llvm::Error Err = Work()) { - reportError(std::move(Err)); - } - }; - - auto warningOS = [argv]() -> raw_ostream & { - return WithColor::warning(errs(), StringRef(argv[0])); - }; - - if (InputFileNames.getNumOccurrences() != 0 && - InputFileNamesDeprecatedOpt.getNumOccurrences() != 0) { - reportError(createStringError( - errc::invalid_argument, - "-inputs and -input cannot be used together, use only -input instead")); - } - if (InputFileNamesDeprecatedOpt.size()) { - warningOS() << "-inputs is deprecated, use -input instead\n"; - // temporary hack to support -inputs - std::vector &s = InputFileNames; - s.insert(s.end(), InputFileNamesDeprecatedOpt.begin(), - InputFileNamesDeprecatedOpt.end()); - } - - if (OutputFileNames.getNumOccurrences() != 0 && - OutputFileNamesDeprecatedOpt.getNumOccurrences() != 0) { - reportError(createStringError(errc::invalid_argument, - "-outputs and -output cannot be used " - "together, use only -output instead")); - } - if (OutputFileNamesDeprecatedOpt.size()) { - warningOS() << "-outputs is deprecated, use -output instead\n"; - // temporary hack to support -outputs - std::vector &s = OutputFileNames; - s.insert(s.end(), OutputFileNamesDeprecatedOpt.begin(), - OutputFileNamesDeprecatedOpt.end()); - } - - if (ListBundleIDs) { - if (Unbundle) { - reportError( - createStringError(errc::invalid_argument, - "-unbundle and -list cannot be used together")); - } - if (InputFileNames.size() != 1) { - reportError(createStringError(errc::invalid_argument, - "only one input file supported for -list")); - } - if (OutputFileNames.size()) { - reportError(createStringError(errc::invalid_argument, - "-outputs option is invalid for -list")); - } - if (TargetNames.size()) { - reportError(createStringError(errc::invalid_argument, - "-targets option is invalid for -list")); - } - - doWork([]() { return ListBundleIDsInFile(InputFileNames.front()); }); - return 0; - } - - if (OutputFileNames.size() == 0) { - reportError( - createStringError(errc::invalid_argument, "no output file specified!")); - } - if (TargetNames.getNumOccurrences() == 0) { - reportError(createStringError( - errc::invalid_argument, - "for the --targets option: must be specified at least once!")); - } - if (Unbundle) { - if (InputFileNames.size() != 1) { - reportError(createStringError( - errc::invalid_argument, - "only one input file supported in unbundling mode")); - } - if (OutputFileNames.size() != TargetNames.size()) { - reportError(createStringError(errc::invalid_argument, - "number of output files and targets should " - "match in unbundling mode")); - } - } else { - if (FilesType == "a") { - reportError(createStringError(errc::invalid_argument, - "Archive files are only supported " - "for unbundling")); - } - if (OutputFileNames.size() != 1) { - reportError(createStringError( - errc::invalid_argument, - "only one output file supported in bundling mode")); - } - if (InputFileNames.size() != TargetNames.size()) { - reportError(createStringError( - errc::invalid_argument, - "number of input files and targets should match in bundling mode")); - } - } - - // Verify that the offload kinds and triples are known. We also check that we - // have exactly one host target. - unsigned Index = 0u; - unsigned HostTargetNum = 0u; - bool HIPOnly = true; - llvm::DenseSet ParsedTargets; - for (StringRef Target : TargetNames) { - if (ParsedTargets.contains(Target)) { - reportError(createStringError(errc::invalid_argument, - "Duplicate targets are not allowed")); - } - ParsedTargets.insert(Target); - - auto OffloadInfo = OffloadTargetInfo(Target); - bool KindIsValid = OffloadInfo.isOffloadKindValid(); - bool TripleIsValid = OffloadInfo.isTripleValid(); - - if (!KindIsValid || !TripleIsValid) { - SmallVector Buf; - raw_svector_ostream Msg(Buf); - Msg << "invalid target '" << Target << "'"; - if (!KindIsValid) - Msg << ", unknown offloading kind '" << OffloadInfo.OffloadKind << "'"; - if (!TripleIsValid) - Msg << ", unknown target triple '" << OffloadInfo.Triple.str() << "'"; - reportError(createStringError(errc::invalid_argument, Msg.str())); - } - - if (KindIsValid && OffloadInfo.hasHostKind()) { - ++HostTargetNum; - // Save the index of the input that refers to the host. - HostInputIndex = Index; - } - - if (OffloadInfo.OffloadKind != "hip" && OffloadInfo.OffloadKind != "hipv4") - HIPOnly = false; - - ++Index; - } - - // HIP uses clang-offload-bundler to bundle device-only compilation results - // for multiple GPU archs, therefore allow no host target if all entries - // are for HIP. - AllowNoHost = HIPOnly; - - // Host triple is not really needed for unbundling operation, so do not - // treat missing host triple as error if we do unbundling. - if ((Unbundle && HostTargetNum > 1) || - (!Unbundle && HostTargetNum != 1 && !AllowNoHost)) { - reportError(createStringError(errc::invalid_argument, - "expecting exactly one host target but got " + - Twine(HostTargetNum))); - } - - doWork([]() { - if (Unbundle) { - if (FilesType == "a") - return UnbundleArchive(); - else - return UnbundleFiles(); - } else - return BundleFiles(); - }); - return 0; -} diff --git a/clang/tools/clang-offload-bundler/CMakeLists.txt b/clang/tools/clang-offload-bundler/CMakeLists.txt --- a/clang/tools/clang-offload-bundler/CMakeLists.txt +++ b/clang/tools/clang-offload-bundler/CMakeLists.txt @@ -2,15 +2,16 @@ add_clang_tool(clang-offload-bundler ClangOffloadBundler.cpp - + DEPENDS intrinsics_gen ) set(CLANG_OFFLOAD_BUNDLER_LIB_DEPS clangBasic + clangDriver ) - + add_dependencies(clang clang-offload-bundler) clang_target_link_libraries(clang-offload-bundler diff --git a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp --- a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp @@ -7,15 +7,14 @@ //===----------------------------------------------------------------------===// /// /// \file -/// This file implements a clang-offload-bundler that bundles different -/// files that relate with the same source code but different targets into a -/// single one. Also the implements the opposite functionality, i.e. unbundle -/// files previous created by this tool. +/// This file implements a stand-alone clang-offload-bundler tool using the +/// OffloadBundler API. /// //===----------------------------------------------------------------------===// #include "clang/Basic/Cuda.h" #include "clang/Basic/Version.h" +#include "clang/Driver/OffloadBundler.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -55,38 +54,45 @@ using namespace llvm; using namespace llvm::object; +using namespace clang; -static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); +static void PrintVersion(raw_ostream &OS) { + OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n'; +} + +int main(int argc, const char **argv) { + + cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); -// Mark all our options with this category, everything else (except for -version -// and -help) will be hidden. -static cl::OptionCategory + // Mark all our options with this category, everything else (except for + // -version and -help) will be hidden. + cl::OptionCategory ClangOffloadBundlerCategory("clang-offload-bundler options"); -static cl::list + cl::list InputFileNames("input", cl::desc("Input file." " Can be specified multiple times " "for multiple input files."), cl::cat(ClangOffloadBundlerCategory)); -static cl::list + cl::list InputFileNamesDeprecatedOpt("inputs", cl::CommaSeparated, cl::desc("[,...] (deprecated)"), cl::cat(ClangOffloadBundlerCategory)); -static cl::list + cl::list OutputFileNames("output", cl::desc("Output file." " Can be specified multiple times " "for multiple output files."), cl::cat(ClangOffloadBundlerCategory)); -static cl::list + cl::list OutputFileNamesDeprecatedOpt("outputs", cl::CommaSeparated, cl::desc("[,...] (deprecated)"), cl::cat(ClangOffloadBundlerCategory)); -static cl::list + cl::list TargetNames("targets", cl::CommaSeparated, cl::desc("[-,...]"), cl::cat(ClangOffloadBundlerCategory)); -static cl::opt + cl::opt FilesType("type", cl::Required, cl::desc("Type of the files to be bundled/unbundled.\n" "Current supported types are:\n" @@ -102,1265 +108,34 @@ " gch - precompiled-header\n" " ast - clang AST file"), cl::cat(ClangOffloadBundlerCategory)); -static cl::opt + cl::opt Unbundle("unbundle", cl::desc("Unbundle bundled file into several output files.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt + cl::opt ListBundleIDs("list", cl::desc("List bundle IDs in the bundled file.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt PrintExternalCommands( + cl::opt PrintExternalCommands( "###", cl::desc("Print any external commands that are to be executed " "instead of actually executing them - for testing purposes.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt + cl::opt AllowMissingBundles("allow-missing-bundles", cl::desc("Create empty files if bundles are missing " "when unbundling.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt + cl::opt BundleAlignment("bundle-align", cl::desc("Alignment of bundle for binary files"), cl::init(1), cl::cat(ClangOffloadBundlerCategory)); - -static cl::opt HipOpenmpCompatible( + cl::opt HipOpenmpCompatible( "hip-openmp-compatible", cl::desc("Treat hip and hipv4 offload kinds as " "compatible with openmp kind, and vice versa.\n"), cl::init(false), cl::cat(ClangOffloadBundlerCategory)); -/// Magic string that marks the existence of offloading data. -#define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" - -/// The index of the host input in the list of inputs. -static unsigned HostInputIndex = ~0u; - -/// Whether not having host target is allowed. -static bool AllowNoHost = false; - -/// Path to the current binary. -static std::string BundlerExecutable; - -/// Obtain the offload kind, real machine triple, and an optional GPUArch -/// out of the target information specified by the user. -/// Bundle Entry ID (or, Offload Target String) has following components: -/// * Offload Kind - Host, OpenMP, or HIP -/// * Triple - Standard LLVM Triple -/// * GPUArch (Optional) - Processor name, like gfx906 or sm_30 - -struct OffloadTargetInfo { - StringRef OffloadKind; - llvm::Triple Triple; - StringRef GPUArch; - - OffloadTargetInfo(const StringRef Target) { - auto TargetFeatures = Target.split(':'); - auto TripleOrGPU = TargetFeatures.first.rsplit('-'); - - if (clang::StringToCudaArch(TripleOrGPU.second) != - clang::CudaArch::UNKNOWN) { - auto KindTriple = TripleOrGPU.first.split('-'); - this->OffloadKind = KindTriple.first; - this->Triple = llvm::Triple(KindTriple.second); - this->GPUArch = Target.substr(Target.find(TripleOrGPU.second)); - } else { - auto KindTriple = TargetFeatures.first.split('-'); - this->OffloadKind = KindTriple.first; - this->Triple = llvm::Triple(KindTriple.second); - this->GPUArch = ""; - } - } - - bool hasHostKind() const { return this->OffloadKind == "host"; } - - bool isOffloadKindValid() const { - return OffloadKind == "host" || OffloadKind == "openmp" || - OffloadKind == "hip" || OffloadKind == "hipv4"; - } - - bool isOffloadKindCompatible(const StringRef TargetOffloadKind) const { - if (OffloadKind == TargetOffloadKind) - return true; - if (HipOpenmpCompatible) { - bool HIPCompatibleWithOpenMP = - OffloadKind.startswith_insensitive("hip") && - TargetOffloadKind == "openmp"; - bool OpenMPCompatibleWithHIP = - OffloadKind == "openmp" && - TargetOffloadKind.startswith_insensitive("hip"); - return HIPCompatibleWithOpenMP || OpenMPCompatibleWithHIP; - } - return false; - } - - bool isTripleValid() const { - return !Triple.str().empty() && Triple.getArch() != Triple::UnknownArch; - } - - bool operator==(const OffloadTargetInfo &Target) const { - return OffloadKind == Target.OffloadKind && - Triple.isCompatibleWith(Target.Triple) && GPUArch == Target.GPUArch; - } - - std::string str() { - return Twine(OffloadKind + "-" + Triple.str() + "-" + GPUArch).str(); - } -}; - -static StringRef getDeviceFileExtension(StringRef Device, - StringRef BundleFileName) { - if (Device.contains("gfx")) - return ".bc"; - if (Device.contains("sm_")) - return ".cubin"; - return sys::path::extension(BundleFileName); -} - -static std::string getDeviceLibraryFileName(StringRef BundleFileName, - StringRef Device) { - StringRef LibName = sys::path::stem(BundleFileName); - StringRef Extension = getDeviceFileExtension(Device, BundleFileName); - - std::string Result; - Result += LibName; - Result += Extension; - return Result; -} - -/// Generic file handler interface. -class FileHandler { -public: - struct BundleInfo { - StringRef BundleID; - }; - - FileHandler() {} - - virtual ~FileHandler() {} - - /// Update the file handler with information from the header of the bundled - /// file. - virtual Error ReadHeader(MemoryBuffer &Input) = 0; - - /// Read the marker of the next bundled to be read in the file. The bundle - /// name is returned if there is one in the file, or `None` if there are no - /// more bundles to be read. - virtual Expected> - ReadBundleStart(MemoryBuffer &Input) = 0; - - /// Read the marker that closes the current bundle. - virtual Error ReadBundleEnd(MemoryBuffer &Input) = 0; - - /// Read the current bundle and write the result into the stream \a OS. - virtual Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) = 0; - - /// Write the header of the bundled file to \a OS based on the information - /// gathered from \a Inputs. - virtual Error WriteHeader(raw_fd_ostream &OS, - ArrayRef> Inputs) = 0; - - /// Write the marker that initiates a bundle for the triple \a TargetTriple to - /// \a OS. - virtual Error WriteBundleStart(raw_fd_ostream &OS, - StringRef TargetTriple) = 0; - - /// Write the marker that closes a bundle for the triple \a TargetTriple to \a - /// OS. - virtual Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) = 0; - - /// Write the bundle from \a Input into \a OS. - virtual Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) = 0; - - /// List bundle IDs in \a Input. - virtual Error listBundleIDs(MemoryBuffer &Input) { - if (Error Err = ReadHeader(Input)) - return Err; - - return forEachBundle(Input, [&](const BundleInfo &Info) -> Error { - llvm::outs() << Info.BundleID << '\n'; - Error Err = listBundleIDsCallback(Input, Info); - if (Err) - return Err; - return Error::success(); - }); - } - - /// For each bundle in \a Input, do \a Func. - Error forEachBundle(MemoryBuffer &Input, - std::function Func) { - while (true) { - Expected> CurTripleOrErr = ReadBundleStart(Input); - if (!CurTripleOrErr) - return CurTripleOrErr.takeError(); - - // No more bundles. - if (!*CurTripleOrErr) - break; - - StringRef CurTriple = **CurTripleOrErr; - assert(!CurTriple.empty()); - - BundleInfo Info{CurTriple}; - if (Error Err = Func(Info)) - return Err; - } - return Error::success(); - } - -protected: - virtual Error listBundleIDsCallback(MemoryBuffer &Input, - const BundleInfo &Info) { - return Error::success(); - } -}; - -/// Handler for binary files. The bundled file will have the following format -/// (all integers are stored in little-endian format): -/// -/// "OFFLOAD_BUNDLER_MAGIC_STR" (ASCII encoding of the string) -/// -/// NumberOfOffloadBundles (8-byte integer) -/// -/// OffsetOfBundle1 (8-byte integer) -/// SizeOfBundle1 (8-byte integer) -/// NumberOfBytesInTripleOfBundle1 (8-byte integer) -/// TripleOfBundle1 (byte length defined before) -/// -/// ... -/// -/// OffsetOfBundleN (8-byte integer) -/// SizeOfBundleN (8-byte integer) -/// NumberOfBytesInTripleOfBundleN (8-byte integer) -/// TripleOfBundleN (byte length defined before) -/// -/// Bundle1 -/// ... -/// BundleN - -/// Read 8-byte integers from a buffer in little-endian format. -static uint64_t Read8byteIntegerFromBuffer(StringRef Buffer, size_t pos) { - uint64_t Res = 0; - const char *Data = Buffer.data(); - - for (unsigned i = 0; i < 8; ++i) { - Res <<= 8; - uint64_t Char = (uint64_t)Data[pos + 7 - i]; - Res |= 0xffu & Char; - } - return Res; -} - -/// Write 8-byte integers to a buffer in little-endian format. -static void Write8byteIntegerToBuffer(raw_fd_ostream &OS, uint64_t Val) { - for (unsigned i = 0; i < 8; ++i) { - char Char = (char)(Val & 0xffu); - OS.write(&Char, 1); - Val >>= 8; - } -} - -class BinaryFileHandler final : public FileHandler { - /// Information about the bundles extracted from the header. - struct BinaryBundleInfo final : public BundleInfo { - /// Size of the bundle. - uint64_t Size = 0u; - /// Offset at which the bundle starts in the bundled file. - uint64_t Offset = 0u; - - BinaryBundleInfo() {} - BinaryBundleInfo(uint64_t Size, uint64_t Offset) - : Size(Size), Offset(Offset) {} - }; - - /// Map between a triple and the corresponding bundle information. - StringMap BundlesInfo; - - /// Iterator for the bundle information that is being read. - StringMap::iterator CurBundleInfo; - StringMap::iterator NextBundleInfo; - - /// Current bundle target to be written. - std::string CurWriteBundleTarget; - -public: - BinaryFileHandler() {} - - ~BinaryFileHandler() final {} - - Error ReadHeader(MemoryBuffer &Input) final { - StringRef FC = Input.getBuffer(); - - // Initialize the current bundle with the end of the container. - CurBundleInfo = BundlesInfo.end(); - - // Check if buffer is smaller than magic string. - size_t ReadChars = sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; - if (ReadChars > FC.size()) - return Error::success(); - - // Check if no magic was found. - StringRef Magic(FC.data(), sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); - if (!Magic.equals(OFFLOAD_BUNDLER_MAGIC_STR)) - return Error::success(); - - // Read number of bundles. - if (ReadChars + 8 > FC.size()) - return Error::success(); - - uint64_t NumberOfBundles = Read8byteIntegerFromBuffer(FC, ReadChars); - ReadChars += 8; - - // Read bundle offsets, sizes and triples. - for (uint64_t i = 0; i < NumberOfBundles; ++i) { - - // Read offset. - if (ReadChars + 8 > FC.size()) - return Error::success(); - - uint64_t Offset = Read8byteIntegerFromBuffer(FC, ReadChars); - ReadChars += 8; - - // Read size. - if (ReadChars + 8 > FC.size()) - return Error::success(); - - uint64_t Size = Read8byteIntegerFromBuffer(FC, ReadChars); - ReadChars += 8; - - // Read triple size. - if (ReadChars + 8 > FC.size()) - return Error::success(); - - uint64_t TripleSize = Read8byteIntegerFromBuffer(FC, ReadChars); - ReadChars += 8; - - // Read triple. - if (ReadChars + TripleSize > FC.size()) - return Error::success(); - - StringRef Triple(&FC.data()[ReadChars], TripleSize); - ReadChars += TripleSize; - - // Check if the offset and size make sense. - if (!Offset || Offset + Size > FC.size()) - return Error::success(); - - assert(BundlesInfo.find(Triple) == BundlesInfo.end() && - "Triple is duplicated??"); - BundlesInfo[Triple] = BinaryBundleInfo(Size, Offset); - } - // Set the iterator to where we will start to read. - CurBundleInfo = BundlesInfo.end(); - NextBundleInfo = BundlesInfo.begin(); - return Error::success(); - } - - Expected> ReadBundleStart(MemoryBuffer &Input) final { - if (NextBundleInfo == BundlesInfo.end()) - return None; - CurBundleInfo = NextBundleInfo++; - return CurBundleInfo->first(); - } - - Error ReadBundleEnd(MemoryBuffer &Input) final { - assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!"); - return Error::success(); - } - - Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { - assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!"); - StringRef FC = Input.getBuffer(); - OS.write(FC.data() + CurBundleInfo->second.Offset, - CurBundleInfo->second.Size); - return Error::success(); - } - - Error WriteHeader(raw_fd_ostream &OS, - ArrayRef> Inputs) final { - // Compute size of the header. - uint64_t HeaderSize = 0; - - HeaderSize += sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; - HeaderSize += 8; // Number of Bundles - - for (auto &T : TargetNames) { - HeaderSize += 3 * 8; // Bundle offset, Size of bundle and size of triple. - HeaderSize += T.size(); // The triple. - } - - // Write to the buffer the header. - OS << OFFLOAD_BUNDLER_MAGIC_STR; - - Write8byteIntegerToBuffer(OS, TargetNames.size()); - - unsigned Idx = 0; - for (auto &T : TargetNames) { - MemoryBuffer &MB = *Inputs[Idx++]; - HeaderSize = alignTo(HeaderSize, BundleAlignment); - // Bundle offset. - Write8byteIntegerToBuffer(OS, HeaderSize); - // Size of the bundle (adds to the next bundle's offset) - Write8byteIntegerToBuffer(OS, MB.getBufferSize()); - BundlesInfo[T] = BinaryBundleInfo(MB.getBufferSize(), HeaderSize); - HeaderSize += MB.getBufferSize(); - // Size of the triple - Write8byteIntegerToBuffer(OS, T.size()); - // Triple - OS << T; - } - return Error::success(); - } - - Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { - CurWriteBundleTarget = TargetTriple.str(); - return Error::success(); - } - - Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { - return Error::success(); - } - - Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { - auto BI = BundlesInfo[CurWriteBundleTarget]; - OS.seek(BI.Offset); - OS.write(Input.getBufferStart(), Input.getBufferSize()); - return Error::success(); - } -}; - -namespace { - -// This class implements a list of temporary files that are removed upon -// object destruction. -class TempFileHandlerRAII { -public: - ~TempFileHandlerRAII() { - for (const auto &File : Files) - sys::fs::remove(File); - } - - // Creates temporary file with given contents. - Expected Create(Optional> Contents) { - SmallString<128u> File; - if (std::error_code EC = - sys::fs::createTemporaryFile("clang-offload-bundler", "tmp", File)) - return createFileError(File, EC); - Files.push_front(File); - - if (Contents) { - std::error_code EC; - raw_fd_ostream OS(File, EC); - if (EC) - return createFileError(File, EC); - OS.write(Contents->data(), Contents->size()); - } - return Files.front().str(); - } - -private: - std::forward_list> Files; -}; - -} // end anonymous namespace - -/// Handler for object files. The bundles are organized by sections with a -/// designated name. -/// -/// To unbundle, we just copy the contents of the designated section. -class ObjectFileHandler final : public FileHandler { - - /// The object file we are currently dealing with. - std::unique_ptr Obj; - - /// Return the input file contents. - StringRef getInputFileContents() const { return Obj->getData(); } - - /// Return bundle name (-) if the provided section is an offload - /// section. - static Expected> IsOffloadSection(SectionRef CurSection) { - Expected NameOrErr = CurSection.getName(); - if (!NameOrErr) - return NameOrErr.takeError(); - - // If it does not start with the reserved suffix, just skip this section. - if (!NameOrErr->startswith(OFFLOAD_BUNDLER_MAGIC_STR)) - return None; - - // Return the triple that is right after the reserved prefix. - return NameOrErr->substr(sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); - } - - /// Total number of inputs. - unsigned NumberOfInputs = 0; - - /// Total number of processed inputs, i.e, inputs that were already - /// read from the buffers. - unsigned NumberOfProcessedInputs = 0; - - /// Iterator of the current and next section. - section_iterator CurrentSection; - section_iterator NextSection; - -public: - ObjectFileHandler(std::unique_ptr ObjIn) - : Obj(std::move(ObjIn)), CurrentSection(Obj->section_begin()), - NextSection(Obj->section_begin()) {} - - ~ObjectFileHandler() final {} - - Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } - - Expected> ReadBundleStart(MemoryBuffer &Input) final { - while (NextSection != Obj->section_end()) { - CurrentSection = NextSection; - ++NextSection; - - // Check if the current section name starts with the reserved prefix. If - // so, return the triple. - Expected> TripleOrErr = - IsOffloadSection(*CurrentSection); - if (!TripleOrErr) - return TripleOrErr.takeError(); - if (*TripleOrErr) - return **TripleOrErr; - } - return None; - } - - Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); } - - Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { - Expected ContentOrErr = CurrentSection->getContents(); - if (!ContentOrErr) - return ContentOrErr.takeError(); - StringRef Content = *ContentOrErr; - - // Copy fat object contents to the output when extracting host bundle. - if (Content.size() == 1u && Content.front() == 0) - Content = StringRef(Input.getBufferStart(), Input.getBufferSize()); - - OS.write(Content.data(), Content.size()); - return Error::success(); - } - - Error WriteHeader(raw_fd_ostream &OS, - ArrayRef> Inputs) final { - assert(HostInputIndex != ~0u && "Host input index not defined."); - - // Record number of inputs. - NumberOfInputs = Inputs.size(); - return Error::success(); - } - - Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { - ++NumberOfProcessedInputs; - return Error::success(); - } - - Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { - assert(NumberOfProcessedInputs <= NumberOfInputs && - "Processing more inputs that actually exist!"); - assert(HostInputIndex != ~0u && "Host input index not defined."); - - // If this is not the last output, we don't have to do anything. - if (NumberOfProcessedInputs != NumberOfInputs) - return Error::success(); - - // We will use llvm-objcopy to add target objects sections to the output - // fat object. These sections should have 'exclude' flag set which tells - // link editor to remove them from linker inputs when linking executable or - // shared library. - - // Find llvm-objcopy in order to create the bundle binary. - ErrorOr Objcopy = sys::findProgramByName( - "llvm-objcopy", sys::path::parent_path(BundlerExecutable)); - if (!Objcopy) - Objcopy = sys::findProgramByName("llvm-objcopy"); - if (!Objcopy) - return createStringError(Objcopy.getError(), - "unable to find 'llvm-objcopy' in path"); - - // We write to the output file directly. So, we close it and use the name - // to pass down to llvm-objcopy. - OS.close(); - - // Temporary files that need to be removed. - TempFileHandlerRAII TempFiles; - - // Compose llvm-objcopy command line for add target objects' sections with - // appropriate flags. - BumpPtrAllocator Alloc; - StringSaver SS{Alloc}; - SmallVector ObjcopyArgs{"llvm-objcopy"}; - for (unsigned I = 0; I < NumberOfInputs; ++I) { - StringRef InputFile = InputFileNames[I]; - if (I == HostInputIndex) { - // Special handling for the host bundle. We do not need to add a - // standard bundle for the host object since we are going to use fat - // object as a host object. Therefore use dummy contents (one zero byte) - // when creating section for the host bundle. - Expected TempFileOrErr = TempFiles.Create(ArrayRef(0)); - if (!TempFileOrErr) - return TempFileOrErr.takeError(); - InputFile = *TempFileOrErr; - } - - ObjcopyArgs.push_back(SS.save(Twine("--add-section=") + - OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + - "=" + InputFile)); - ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + - OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + - "=readonly,exclude")); - } - ObjcopyArgs.push_back("--"); - ObjcopyArgs.push_back(InputFileNames[HostInputIndex]); - ObjcopyArgs.push_back(OutputFileNames.front()); - - if (Error Err = executeObjcopy(*Objcopy, ObjcopyArgs)) - return Err; - - return Error::success(); - } - - Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { - return Error::success(); - } - -private: - static Error executeObjcopy(StringRef Objcopy, ArrayRef Args) { - // If the user asked for the commands to be printed out, we do that - // instead of executing it. - if (PrintExternalCommands) { - errs() << "\"" << Objcopy << "\""; - for (StringRef Arg : drop_begin(Args, 1)) - errs() << " \"" << Arg << "\""; - errs() << "\n"; - } else { - if (sys::ExecuteAndWait(Objcopy, Args)) - return createStringError(inconvertibleErrorCode(), - "'llvm-objcopy' tool failed"); - } - return Error::success(); - } -}; - -/// Handler for text files. The bundled file will have the following format. -/// -/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" -/// Bundle 1 -/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" -/// ... -/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" -/// Bundle N -/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" -class TextFileHandler final : public FileHandler { - /// String that begins a line comment. - StringRef Comment; - - /// String that initiates a bundle. - std::string BundleStartString; - - /// String that closes a bundle. - std::string BundleEndString; - - /// Number of chars read from input. - size_t ReadChars = 0u; - -protected: - Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } - - Expected> ReadBundleStart(MemoryBuffer &Input) final { - StringRef FC = Input.getBuffer(); - - // Find start of the bundle. - ReadChars = FC.find(BundleStartString, ReadChars); - if (ReadChars == FC.npos) - return None; - - // Get position of the triple. - size_t TripleStart = ReadChars = ReadChars + BundleStartString.size(); - - // Get position that closes the triple. - size_t TripleEnd = ReadChars = FC.find("\n", ReadChars); - if (TripleEnd == FC.npos) - return None; - - // Next time we read after the new line. - ++ReadChars; - - return StringRef(&FC.data()[TripleStart], TripleEnd - TripleStart); - } - - Error ReadBundleEnd(MemoryBuffer &Input) final { - StringRef FC = Input.getBuffer(); - - // Read up to the next new line. - assert(FC[ReadChars] == '\n' && "The bundle should end with a new line."); - - size_t TripleEnd = ReadChars = FC.find("\n", ReadChars + 1); - if (TripleEnd != FC.npos) - // Next time we read after the new line. - ++ReadChars; - - return Error::success(); - } - - Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { - StringRef FC = Input.getBuffer(); - size_t BundleStart = ReadChars; - - // Find end of the bundle. - size_t BundleEnd = ReadChars = FC.find(BundleEndString, ReadChars); - - StringRef Bundle(&FC.data()[BundleStart], BundleEnd - BundleStart); - OS << Bundle; - - return Error::success(); - } - - Error WriteHeader(raw_fd_ostream &OS, - ArrayRef> Inputs) final { - return Error::success(); - } - - Error WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) final { - OS << BundleStartString << TargetTriple << "\n"; - return Error::success(); - } - - Error WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) final { - OS << BundleEndString << TargetTriple << "\n"; - return Error::success(); - } - - Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { - OS << Input.getBuffer(); - return Error::success(); - } - -public: - TextFileHandler(StringRef Comment) : Comment(Comment), ReadChars(0) { - BundleStartString = - "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__START__ "; - BundleEndString = - "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__END__ "; - } - - Error listBundleIDsCallback(MemoryBuffer &Input, - const BundleInfo &Info) final { - // TODO: To list bundle IDs in a bundled text file we need to go through - // all bundles. The format of bundled text file may need to include a - // header if the performance of listing bundle IDs of bundled text file is - // important. - ReadChars = Input.getBuffer().find(BundleEndString, ReadChars); - if (Error Err = ReadBundleEnd(Input)) - return Err; - return Error::success(); - } -}; - -/// Return an appropriate object file handler. We use the specific object -/// handler if we know how to deal with that format, otherwise we use a default -/// binary file handler. -static std::unique_ptr -CreateObjectFileHandler(MemoryBuffer &FirstInput) { - // Check if the input file format is one that we know how to deal with. - Expected> BinaryOrErr = createBinary(FirstInput); - - // We only support regular object files. If failed to open the input as a - // known binary or this is not an object file use the default binary handler. - if (errorToBool(BinaryOrErr.takeError()) || !isa(*BinaryOrErr)) - return std::make_unique(); - - // Otherwise create an object file handler. The handler will be owned by the - // client of this function. - return std::make_unique( - std::unique_ptr(cast(BinaryOrErr->release()))); -} - -/// Return an appropriate handler given the input files and options. -static Expected> -CreateFileHandler(MemoryBuffer &FirstInput) { - if (FilesType == "i") - return std::make_unique(/*Comment=*/"//"); - if (FilesType == "ii") - return std::make_unique(/*Comment=*/"//"); - if (FilesType == "cui") - return std::make_unique(/*Comment=*/"//"); - // TODO: `.d` should be eventually removed once `-M` and its variants are - // handled properly in offload compilation. - if (FilesType == "d") - return std::make_unique(/*Comment=*/"#"); - if (FilesType == "ll") - return std::make_unique(/*Comment=*/";"); - if (FilesType == "bc") - return std::make_unique(); - if (FilesType == "s") - return std::make_unique(/*Comment=*/"#"); - if (FilesType == "o") - return CreateObjectFileHandler(FirstInput); - if (FilesType == "a") - return CreateObjectFileHandler(FirstInput); - if (FilesType == "gch") - return std::make_unique(); - if (FilesType == "ast") - return std::make_unique(); - - return createStringError(errc::invalid_argument, - "'" + FilesType + "': invalid file type specified"); -} - -/// Bundle the files. Return true if an error was found. -static Error BundleFiles() { - std::error_code EC; - - // Create output file. - raw_fd_ostream OutputFile(OutputFileNames.front(), EC, sys::fs::OF_None); - if (EC) - return createFileError(OutputFileNames.front(), EC); - - // Open input files. - SmallVector, 8u> InputBuffers; - InputBuffers.reserve(InputFileNames.size()); - for (auto &I : InputFileNames) { - ErrorOr> CodeOrErr = - MemoryBuffer::getFileOrSTDIN(I); - if (std::error_code EC = CodeOrErr.getError()) - return createFileError(I, EC); - InputBuffers.emplace_back(std::move(*CodeOrErr)); - } - - // Get the file handler. We use the host buffer as reference. - assert((HostInputIndex != ~0u || AllowNoHost) && - "Host input index undefined??"); - Expected> FileHandlerOrErr = - CreateFileHandler(*InputBuffers[AllowNoHost ? 0 : HostInputIndex]); - if (!FileHandlerOrErr) - return FileHandlerOrErr.takeError(); - - std::unique_ptr &FH = *FileHandlerOrErr; - assert(FH); - - // Write header. - if (Error Err = FH->WriteHeader(OutputFile, InputBuffers)) - return Err; - - // Write all bundles along with the start/end markers. If an error was found - // writing the end of the bundle component, abort the bundle writing. - auto Input = InputBuffers.begin(); - for (auto &Triple : TargetNames) { - if (Error Err = FH->WriteBundleStart(OutputFile, Triple)) - return Err; - if (Error Err = FH->WriteBundle(OutputFile, **Input)) - return Err; - if (Error Err = FH->WriteBundleEnd(OutputFile, Triple)) - return Err; - ++Input; - } - return Error::success(); -} - -// List bundle IDs. Return true if an error was found. -static Error ListBundleIDsInFile(StringRef InputFileName) { - // Open Input file. - ErrorOr> CodeOrErr = - MemoryBuffer::getFileOrSTDIN(InputFileName); - if (std::error_code EC = CodeOrErr.getError()) - return createFileError(InputFileName, EC); - - MemoryBuffer &Input = **CodeOrErr; - - // Select the right files handler. - Expected> FileHandlerOrErr = - CreateFileHandler(Input); - if (!FileHandlerOrErr) - return FileHandlerOrErr.takeError(); - - std::unique_ptr &FH = *FileHandlerOrErr; - assert(FH); - return FH->listBundleIDs(Input); -} - -// Unbundle the files. Return true if an error was found. -static Error UnbundleFiles() { - // Open Input file. - ErrorOr> CodeOrErr = - MemoryBuffer::getFileOrSTDIN(InputFileNames.front()); - if (std::error_code EC = CodeOrErr.getError()) - return createFileError(InputFileNames.front(), EC); - - MemoryBuffer &Input = **CodeOrErr; - - // Select the right files handler. - Expected> FileHandlerOrErr = - CreateFileHandler(Input); - if (!FileHandlerOrErr) - return FileHandlerOrErr.takeError(); - - std::unique_ptr &FH = *FileHandlerOrErr; - assert(FH); - - // Read the header of the bundled file. - if (Error Err = FH->ReadHeader(Input)) - return Err; - - // Create a work list that consist of the map triple/output file. - StringMap Worklist; - auto Output = OutputFileNames.begin(); - for (auto &Triple : TargetNames) { - Worklist[Triple] = *Output; - ++Output; - } - - // Read all the bundles that are in the work list. If we find no bundles we - // assume the file is meant for the host target. - bool FoundHostBundle = false; - while (!Worklist.empty()) { - Expected> CurTripleOrErr = FH->ReadBundleStart(Input); - if (!CurTripleOrErr) - return CurTripleOrErr.takeError(); - - // We don't have more bundles. - if (!*CurTripleOrErr) - break; - - StringRef CurTriple = **CurTripleOrErr; - assert(!CurTriple.empty()); - - auto Output = Worklist.find(CurTriple); - // The file may have more bundles for other targets, that we don't care - // about. Therefore, move on to the next triple - if (Output == Worklist.end()) - continue; - - // Check if the output file can be opened and copy the bundle to it. - std::error_code EC; - raw_fd_ostream OutputFile(Output->second, EC, sys::fs::OF_None); - if (EC) - return createFileError(Output->second, EC); - if (Error Err = FH->ReadBundle(OutputFile, Input)) - return Err; - if (Error Err = FH->ReadBundleEnd(Input)) - return Err; - Worklist.erase(Output); - - // Record if we found the host bundle. - auto OffloadInfo = OffloadTargetInfo(CurTriple); - if (OffloadInfo.hasHostKind()) - FoundHostBundle = true; - } - - if (!AllowMissingBundles && !Worklist.empty()) { - std::string ErrMsg = "Can't find bundles for"; - std::set Sorted; - for (auto &E : Worklist) - Sorted.insert(E.first()); - unsigned I = 0; - unsigned Last = Sorted.size() - 1; - for (auto &E : Sorted) { - if (I != 0 && Last > 1) - ErrMsg += ","; - ErrMsg += " "; - if (I == Last && I != 0) - ErrMsg += "and "; - ErrMsg += E.str(); - ++I; - } - return createStringError(inconvertibleErrorCode(), ErrMsg); - } - - // If no bundles were found, assume the input file is the host bundle and - // create empty files for the remaining targets. - if (Worklist.size() == TargetNames.size()) { - for (auto &E : Worklist) { - std::error_code EC; - raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); - if (EC) - return createFileError(E.second, EC); - - // If this entry has a host kind, copy the input file to the output file. - auto OffloadInfo = OffloadTargetInfo(E.getKey()); - if (OffloadInfo.hasHostKind()) - OutputFile.write(Input.getBufferStart(), Input.getBufferSize()); - } - return Error::success(); - } - - // If we found elements, we emit an error if none of those were for the host - // in case host bundle name was provided in command line. - if (!FoundHostBundle && HostInputIndex != ~0u) - return createStringError(inconvertibleErrorCode(), - "Can't find bundle for the host target"); - - // If we still have any elements in the worklist, create empty files for them. - for (auto &E : Worklist) { - std::error_code EC; - raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); - if (EC) - return createFileError(E.second, EC); - } - - return Error::success(); -} - -static Archive::Kind getDefaultArchiveKindForHost() { - return Triple(sys::getDefaultTargetTriple()).isOSDarwin() ? Archive::K_DARWIN - : Archive::K_GNU; -} - -/// @brief Checks if a code object \p CodeObjectInfo is compatible with a given -/// target \p TargetInfo. -/// @link https://clang.llvm.org/docs/ClangOffloadBundler.html#bundle-entry-id -bool isCodeObjectCompatible(OffloadTargetInfo &CodeObjectInfo, - OffloadTargetInfo &TargetInfo) { - - // Compatible in case of exact match. - if (CodeObjectInfo == TargetInfo) { - DEBUG_WITH_TYPE("CodeObjectCompatibility", - dbgs() << "Compatible: Exact match: \t[CodeObject: " - << CodeObjectInfo.str() - << "]\t:\t[Target: " << TargetInfo.str() << "]\n"); - return true; - } - - // Incompatible if Kinds or Triples mismatch. - if (!CodeObjectInfo.isOffloadKindCompatible(TargetInfo.OffloadKind) || - !CodeObjectInfo.Triple.isCompatibleWith(TargetInfo.Triple)) { - DEBUG_WITH_TYPE( - "CodeObjectCompatibility", - dbgs() << "Incompatible: Kind/Triple mismatch \t[CodeObject: " - << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() - << "]\n"); - return false; - } - - // Incompatible if GPUArch mismatch. - if (CodeObjectInfo.GPUArch != TargetInfo.GPUArch) { - DEBUG_WITH_TYPE("CodeObjectCompatibility", - dbgs() << "Incompatible: GPU Arch mismatch \t[CodeObject: " - << CodeObjectInfo.str() - << "]\t:\t[Target: " << TargetInfo.str() << "]\n"); - return false; - } - - DEBUG_WITH_TYPE( - "CodeObjectCompatibility", - dbgs() << "Compatible: Code Objects are compatible \t[CodeObject: " - << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() - << "]\n"); - return true; -} - -/// @brief Computes a list of targets among all given targets which are -/// compatible with this code object -/// @param [in] CodeObjectInfo Code Object -/// @param [out] CompatibleTargets List of all compatible targets among all -/// given targets -/// @return false, if no compatible target is found. -static bool -getCompatibleOffloadTargets(OffloadTargetInfo &CodeObjectInfo, - SmallVectorImpl &CompatibleTargets) { - if (!CompatibleTargets.empty()) { - DEBUG_WITH_TYPE("CodeObjectCompatibility", - dbgs() << "CompatibleTargets list should be empty\n"); - return false; - } - for (auto &Target : TargetNames) { - auto TargetInfo = OffloadTargetInfo(Target); - if (isCodeObjectCompatible(CodeObjectInfo, TargetInfo)) - CompatibleTargets.push_back(Target); - } - return !CompatibleTargets.empty(); -} - -/// UnbundleArchive takes an archive file (".a") as input containing bundled -/// code object files, and a list of offload targets (not host), and extracts -/// the code objects into a new archive file for each offload target. Each -/// resulting archive file contains all code object files corresponding to that -/// particular offload target. The created archive file does not -/// contain an index of the symbols and code object files are named as -/// <->, with ':' replaced with '_'. -static Error UnbundleArchive() { - std::vector> ArchiveBuffers; - - /// Map of target names with list of object files that will form the device - /// specific archive for that target - StringMap> OutputArchivesMap; - - // Map of target names and output archive filenames - StringMap TargetOutputFileNameMap; - - auto Output = OutputFileNames.begin(); - for (auto &Target : TargetNames) { - TargetOutputFileNameMap[Target] = *Output; - ++Output; - } - - StringRef IFName = InputFileNames.front(); - ErrorOr> BufOrErr = - MemoryBuffer::getFileOrSTDIN(IFName, true, false); - if (std::error_code EC = BufOrErr.getError()) - return createFileError(InputFileNames.front(), EC); - - ArchiveBuffers.push_back(std::move(*BufOrErr)); - Expected> LibOrErr = - Archive::create(ArchiveBuffers.back()->getMemBufferRef()); - if (!LibOrErr) - return LibOrErr.takeError(); - - auto Archive = std::move(*LibOrErr); - - Error ArchiveErr = Error::success(); - auto ChildEnd = Archive->child_end(); - - /// Iterate over all bundled code object files in the input archive. - for (auto ArchiveIter = Archive->child_begin(ArchiveErr); - ArchiveIter != ChildEnd; ++ArchiveIter) { - if (ArchiveErr) - return ArchiveErr; - auto ArchiveChildNameOrErr = (*ArchiveIter).getName(); - if (!ArchiveChildNameOrErr) - return ArchiveChildNameOrErr.takeError(); - - StringRef BundledObjectFile = sys::path::filename(*ArchiveChildNameOrErr); - - auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef(); - if (!CodeObjectBufferRefOrErr) - return CodeObjectBufferRefOrErr.takeError(); - - auto CodeObjectBuffer = - MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false); - - Expected> FileHandlerOrErr = - CreateFileHandler(*CodeObjectBuffer); - if (!FileHandlerOrErr) - return FileHandlerOrErr.takeError(); - - std::unique_ptr &FileHandler = *FileHandlerOrErr; - assert(FileHandler && - "FileHandle creation failed for file in the archive!"); - - if (Error ReadErr = FileHandler.get()->ReadHeader(*CodeObjectBuffer)) - return ReadErr; - - Expected> CurBundleIDOrErr = - FileHandler->ReadBundleStart(*CodeObjectBuffer); - if (!CurBundleIDOrErr) - return CurBundleIDOrErr.takeError(); - - Optional OptionalCurBundleID = *CurBundleIDOrErr; - // No device code in this child, skip. - if (!OptionalCurBundleID) - continue; - StringRef CodeObject = *OptionalCurBundleID; - - // Process all bundle entries (CodeObjects) found in this child of input - // archive. - while (!CodeObject.empty()) { - SmallVector CompatibleTargets; - auto CodeObjectInfo = OffloadTargetInfo(CodeObject); - if (CodeObjectInfo.hasHostKind()) { - // Do nothing, we don't extract host code yet. - } else if (getCompatibleOffloadTargets(CodeObjectInfo, - CompatibleTargets)) { - std::string BundleData; - raw_string_ostream DataStream(BundleData); - if (Error Err = - FileHandler.get()->ReadBundle(DataStream, *CodeObjectBuffer)) - return Err; - - for (auto &CompatibleTarget : CompatibleTargets) { - SmallString<128> BundledObjectFileName; - BundledObjectFileName.assign(BundledObjectFile); - auto OutputBundleName = - Twine(llvm::sys::path::stem(BundledObjectFileName) + "-" + - CodeObject + - getDeviceLibraryFileName(BundledObjectFileName, - CodeObjectInfo.GPUArch)) - .str(); - // Replace ':' in optional target feature list with '_' to ensure - // cross-platform validity. - std::replace(OutputBundleName.begin(), OutputBundleName.end(), ':', - '_'); - - std::unique_ptr MemBuf = MemoryBuffer::getMemBufferCopy( - DataStream.str(), OutputBundleName); - ArchiveBuffers.push_back(std::move(MemBuf)); - llvm::MemoryBufferRef MemBufRef = - MemoryBufferRef(*(ArchiveBuffers.back())); - - // For inserting > entry in - // OutputArchivesMap. - if (OutputArchivesMap.find(CompatibleTarget) == - OutputArchivesMap.end()) { - - std::vector ArchiveMembers; - ArchiveMembers.push_back(NewArchiveMember(MemBufRef)); - OutputArchivesMap.insert_or_assign(CompatibleTarget, - std::move(ArchiveMembers)); - } else { - OutputArchivesMap[CompatibleTarget].push_back( - NewArchiveMember(MemBufRef)); - } - } - } - - if (Error Err = FileHandler.get()->ReadBundleEnd(*CodeObjectBuffer)) - return Err; - - Expected> NextTripleOrErr = - FileHandler->ReadBundleStart(*CodeObjectBuffer); - if (!NextTripleOrErr) - return NextTripleOrErr.takeError(); - - CodeObject = NextTripleOrErr->value_or(""); - } // End of processing of all bundle entries of this child of input archive. - } // End of while over children of input archive. - - assert(!ArchiveErr && "Error occurred while reading archive!"); - - /// Write out an archive for each target - for (auto &Target : TargetNames) { - StringRef FileName = TargetOutputFileNameMap[Target]; - StringMapIterator> CurArchiveMembers = - OutputArchivesMap.find(Target); - if (CurArchiveMembers != OutputArchivesMap.end()) { - if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(), - true, getDefaultArchiveKindForHost(), - true, false, nullptr)) - return WriteErr; - } else if (!AllowMissingBundles) { - std::string ErrMsg = - Twine("no compatible code object found for the target '" + Target + - "' in heterogeneous archive library: " + IFName) - .str(); - return createStringError(inconvertibleErrorCode(), ErrMsg); - } else { // Create an empty archive file if no compatible code object is - // found and "allow-missing-bundles" is enabled. It ensures that - // the linker using output of this step doesn't complain about - // the missing input file. - std::vector EmptyArchive; - EmptyArchive.clear(); - if (Error WriteErr = writeArchive(FileName, EmptyArchive, true, - getDefaultArchiveKindForHost(), true, - false, nullptr)) - return WriteErr; - } - } - - return Error::success(); -} - -static void PrintVersion(raw_ostream &OS) { - OS << clang::getClangToolFullVersion("clang-offload-bundler") << '\n'; -} - -int main(int argc, const char **argv) { + // Process commandline options and report errors sys::PrintStackTraceOnErrorSignal(argv[0]); cl::HideUnrelatedOptions(ClangOffloadBundlerCategory); @@ -1377,19 +152,32 @@ return 0; } + /// Class to store bundler options in standard (non-cl::opt) data structures + // Avoid using cl::opt variables after these assignments when possible + OffloadBundlerConfig BundlerConfig; + BundlerConfig.AllowMissingBundles = AllowMissingBundles; + BundlerConfig.PrintExternalCommands = PrintExternalCommands; + BundlerConfig.HipOpenmpCompatible = HipOpenmpCompatible; + BundlerConfig.BundleAlignment = BundleAlignment; + BundlerConfig.FilesType = FilesType; + BundlerConfig.ObjcopyPath = ""; + + BundlerConfig.TargetNames = TargetNames; + BundlerConfig.InputFileNames = InputFileNames; + BundlerConfig.OutputFileNames = OutputFileNames; + + /// The index of the host input in the list of inputs. + BundlerConfig.HostInputIndex = ~0u; + + /// Whether not having host target is allowed. + BundlerConfig.AllowNoHost = false; + auto reportError = [argv](Error E) { logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0])); exit(1); }; auto doWork = [&](std::function Work) { - // Save the current executable directory as it will be useful to find other - // tools. - BundlerExecutable = argv[0]; - if (!llvm::sys::fs::exists(BundlerExecutable)) - BundlerExecutable = - sys::fs::getMainExecutable(argv[0], &BundlerExecutable); - if (llvm::Error Err = Work()) { reportError(std::move(Err)); } @@ -1399,12 +187,32 @@ return WithColor::warning(errs(), StringRef(argv[0])); }; + /// Path to the current binary. + std::string BundlerExecutable = argv[0]; + + if (!llvm::sys::fs::exists(BundlerExecutable)) + BundlerExecutable = + sys::fs::getMainExecutable(argv[0], &BundlerExecutable); + + // Find llvm-objcopy in order to create the bundle binary. + ErrorOr Objcopy = sys::findProgramByName( + "llvm-objcopy", + sys::path::parent_path(BundlerExecutable)); + if (!Objcopy) + Objcopy = sys::findProgramByName("llvm-objcopy"); + if (!Objcopy) + reportError(createStringError(Objcopy.getError(), + "unable to find 'llvm-objcopy' in path")); + else + BundlerConfig.ObjcopyPath = *Objcopy; + if (InputFileNames.getNumOccurrences() != 0 && InputFileNamesDeprecatedOpt.getNumOccurrences() != 0) { reportError(createStringError( errc::invalid_argument, "-inputs and -input cannot be used together, use only -input instead")); } + if (InputFileNamesDeprecatedOpt.size()) { warningOS() << "-inputs is deprecated, use -input instead\n"; // temporary hack to support -inputs @@ -1412,6 +220,7 @@ s.insert(s.end(), InputFileNamesDeprecatedOpt.begin(), InputFileNamesDeprecatedOpt.end()); } + BundlerConfig.InputFileNames = InputFileNames; if (OutputFileNames.getNumOccurrences() != 0 && OutputFileNamesDeprecatedOpt.getNumOccurrences() != 0) { @@ -1419,6 +228,7 @@ "-outputs and -output cannot be used " "together, use only -output instead")); } + if (OutputFileNamesDeprecatedOpt.size()) { warningOS() << "-outputs is deprecated, use -output instead\n"; // temporary hack to support -outputs @@ -1426,6 +236,7 @@ s.insert(s.end(), OutputFileNamesDeprecatedOpt.begin(), OutputFileNamesDeprecatedOpt.end()); } + BundlerConfig.OutputFileNames = OutputFileNames; if (ListBundleIDs) { if (Unbundle) { @@ -1446,7 +257,9 @@ "-targets option is invalid for -list")); } - doWork([]() { return ListBundleIDsInFile(InputFileNames.front()); }); + doWork([&]() { return OffloadBundler::ListBundleIDsInFile( + InputFileNames.front(), + BundlerConfig); }); return 0; } @@ -1454,11 +267,13 @@ reportError( createStringError(errc::invalid_argument, "no output file specified!")); } + if (TargetNames.getNumOccurrences() == 0) { reportError(createStringError( errc::invalid_argument, "for the --targets option: must be specified at least once!")); } + if (Unbundle) { if (InputFileNames.size() != 1) { reportError(createStringError( @@ -1471,7 +286,7 @@ "match in unbundling mode")); } } else { - if (FilesType == "a") { + if (BundlerConfig.FilesType == "a") { reportError(createStringError(errc::invalid_argument, "Archive files are only supported " "for unbundling")); @@ -1501,7 +316,7 @@ } ParsedTargets.insert(Target); - auto OffloadInfo = OffloadTargetInfo(Target); + auto OffloadInfo = OffloadTargetInfo(Target, BundlerConfig); bool KindIsValid = OffloadInfo.isOffloadKindValid(); bool TripleIsValid = OffloadInfo.isTripleValid(); @@ -1519,7 +334,7 @@ if (KindIsValid && OffloadInfo.hasHostKind()) { ++HostTargetNum; // Save the index of the input that refers to the host. - HostInputIndex = Index; + BundlerConfig.HostInputIndex = Index; } if (OffloadInfo.OffloadKind != "hip" && OffloadInfo.OffloadKind != "hipv4") @@ -1531,25 +346,27 @@ // HIP uses clang-offload-bundler to bundle device-only compilation results // for multiple GPU archs, therefore allow no host target if all entries // are for HIP. - AllowNoHost = HIPOnly; + BundlerConfig.AllowNoHost = HIPOnly; // Host triple is not really needed for unbundling operation, so do not // treat missing host triple as error if we do unbundling. if ((Unbundle && HostTargetNum > 1) || - (!Unbundle && HostTargetNum != 1 && !AllowNoHost)) { + (!Unbundle && HostTargetNum != 1 && !BundlerConfig.AllowNoHost)) { reportError(createStringError(errc::invalid_argument, "expecting exactly one host target but got " + Twine(HostTargetNum))); } - doWork([]() { + OffloadBundler Bundler(BundlerConfig); + + doWork([&]() { if (Unbundle) { - if (FilesType == "a") - return UnbundleArchive(); + if (BundlerConfig.FilesType == "a") + return Bundler.UnbundleArchive(); else - return UnbundleFiles(); + return Bundler.UnbundleFiles(); } else - return BundleFiles(); + return Bundler.BundleFiles(); }); return 0; }