diff --git a/clang/include/clang/Driver/OffloadBundler.h b/clang/include/clang/Driver/OffloadBundler.h new file mode 100644 index 00000000000000..ea0631b0ddf7f0 --- /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 index 18c9b2d042f6c6..4817bcfbcb8186 100644 --- 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 @@ add_clang_library(clangDriver DriverOptions.cpp Job.cpp Multilib.cpp + OffloadBundler.cpp OptionUtils.cpp Phases.cpp SanitizerArgs.cpp diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp new file mode 100644 index 00000000000000..6e485c848e74ea --- /dev/null +++ b/clang/lib/Driver/OffloadBundler.cpp @@ -0,0 +1,1288 @@ +//===- 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. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// 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" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Object/Archive.h" +#include "llvm/Object/ArchiveWriter.h" +#include "llvm/Object/Binary.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/StringSaver.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace llvm; +using namespace llvm::object; +using namespace clang; + +/// Magic string that marks the existence of offloading data. +#define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" + +OffloadTargetInfo::OffloadTargetInfo(const StringRef Target, + const OffloadBundlerConfig &BC) + : BundlerConfig(BC) { + + // TODO: Add error checking from ClangOffloadBundler.cpp + 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 OffloadTargetInfo::hasHostKind() const { + return this->OffloadKind == "host"; +} + +bool OffloadTargetInfo::isOffloadKindValid() const { + return OffloadKind == "host" || OffloadKind == "openmp" || + OffloadKind == "hip" || OffloadKind == "hipv4"; +} + +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; +} + +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) { + 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; + + /// Configuration options and arrays for this bundler job + const OffloadBundlerConfig &BundlerConfig; + +public: + // TODO: Add error checking from ClangOffloadBundler.cpp + BinaryFileHandler(const OffloadBundlerConfig &BC) : BundlerConfig(BC) {} + + ~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 : BundlerConfig.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, BundlerConfig.TargetNames.size()); + + unsigned Idx = 0; + for (auto &T : BundlerConfig.TargetNames) { + MemoryBuffer &MB = *Inputs[Idx++]; + HeaderSize = alignTo(HeaderSize, BundlerConfig.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; + + /// Configuration options and arrays for this bundler job + const OffloadBundlerConfig &BundlerConfig; + +public: + // 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()), BundlerConfig(BC) {} + + ~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(BundlerConfig.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(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) + 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. + + 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. + 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 = 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) + // 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 + + BundlerConfig.TargetNames[I] + + "=" + InputFile)); + ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + + OFFLOAD_BUNDLER_MAGIC_STR + + BundlerConfig.TargetNames[I] + + "=readonly,exclude")); + } + ObjcopyArgs.push_back("--"); + ObjcopyArgs.push_back( + BundlerConfig.InputFileNames[BundlerConfig.HostInputIndex]); + ObjcopyArgs.push_back(BundlerConfig.OutputFileNames.front()); + + if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs)) + return Err; + + return Error::success(); + } + + Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { + return Error::success(); + } + +private: + 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 (BundlerConfig.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, + 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(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())), + BundlerConfig); +} + +/// Return an appropriate handler given the input files and options. +static Expected> +CreateFileHandler(MemoryBuffer &FirstInput, + const OffloadBundlerConfig &BundlerConfig) { + std::string FilesType = BundlerConfig.FilesType; + + 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(BundlerConfig); + if (FilesType == "s") + return std::make_unique(/*Comment=*/"#"); + if (FilesType == "o") + return CreateObjectFileHandler(FirstInput, BundlerConfig); + if (FilesType == "a") + return CreateObjectFileHandler(FirstInput, BundlerConfig); + if (FilesType == "gch") + return std::make_unique(BundlerConfig); + if (FilesType == "ast") + 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. +Error OffloadBundler::BundleFiles() { + std::error_code EC; + + // Create output file. + raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), + EC, sys::fs::OF_None); + if (EC) + return createFileError(BundlerConfig.OutputFileNames.front(), EC); + + // Open input files. + SmallVector, 8u> InputBuffers; + InputBuffers.reserve(BundlerConfig.InputFileNames.size()); + for (auto &I : BundlerConfig.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((BundlerConfig.HostInputIndex != ~0u || BundlerConfig.AllowNoHost) && + "Host input index undefined??"); + Expected> FileHandlerOrErr = + CreateFileHandler(*InputBuffers[BundlerConfig.AllowNoHost ? 0 + : BundlerConfig.HostInputIndex], + BundlerConfig); + 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 : BundlerConfig.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(); +} + +// Unbundle the files. Return true if an error was found. +Error OffloadBundler::UnbundleFiles() { + // Open Input file. + ErrorOr> CodeOrErr = + MemoryBuffer::getFileOrSTDIN(BundlerConfig.InputFileNames.front()); + if (std::error_code EC = CodeOrErr.getError()) + return createFileError(BundlerConfig.InputFileNames.front(), 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); + + // 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 = BundlerConfig.OutputFileNames.begin(); + for (auto &Triple : BundlerConfig.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, BundlerConfig); + if (OffloadInfo.hasHostKind()) + FoundHostBundle = true; + } + + if (!BundlerConfig.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() == BundlerConfig.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(), BundlerConfig); + 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 && BundlerConfig.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] 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, + const OffloadBundlerConfig &BundlerConfig) { + if (!CompatibleTargets.empty()) { + DEBUG_WITH_TYPE("CodeObjectCompatibility", + dbgs() << "CompatibleTargets list should be empty\n"); + return false; + } + for (auto &Target : BundlerConfig.TargetNames) { + auto TargetInfo = OffloadTargetInfo(Target, BundlerConfig); + 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 '_'. +Error OffloadBundler::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 = BundlerConfig.OutputFileNames.begin(); + for (auto &Target : BundlerConfig.TargetNames) { + TargetOutputFileNameMap[Target] = *Output; + ++Output; + } + + StringRef IFName = BundlerConfig.InputFileNames.front(); + + ErrorOr> BufOrErr = + MemoryBuffer::getFileOrSTDIN(IFName, true, false); + if (std::error_code EC = BufOrErr.getError()) + return createFileError(BundlerConfig.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, BundlerConfig); + 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.hasValue()) + 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, BundlerConfig); + if (CodeObjectInfo.hasHostKind()) { + // Do nothing, we don't extract host code yet. + } else if (getCompatibleOffloadTargets(CodeObjectInfo, + CompatibleTargets, + BundlerConfig)) { + 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).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 : BundlerConfig.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 (!BundlerConfig.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(); +} diff --git a/clang/tools/clang-offload-bundler/CMakeLists.txt b/clang/tools/clang-offload-bundler/CMakeLists.txt index 2738bf02e729fe..12f00eeb947e99 100644 --- a/clang/tools/clang-offload-bundler/CMakeLists.txt +++ b/clang/tools/clang-offload-bundler/CMakeLists.txt @@ -2,15 +2,16 @@ set(LLVM_LINK_COMPONENTS Object Support) 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 index 77370ed8342dfb..94973158b44147 100644 --- 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 @@ static cl::opt " 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 @@ int main(int argc, const char **argv) { 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 @@ int main(int argc, const char **argv) { 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 @@ int main(int argc, const char **argv) { s.insert(s.end(), InputFileNamesDeprecatedOpt.begin(), InputFileNamesDeprecatedOpt.end()); } + BundlerConfig.InputFileNames = InputFileNames; if (OutputFileNames.getNumOccurrences() != 0 && OutputFileNamesDeprecatedOpt.getNumOccurrences() != 0) { @@ -1419,6 +228,7 @@ int main(int argc, const char **argv) { "-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 @@ int main(int argc, const char **argv) { s.insert(s.end(), OutputFileNamesDeprecatedOpt.begin(), OutputFileNamesDeprecatedOpt.end()); } + BundlerConfig.OutputFileNames = OutputFileNames; if (ListBundleIDs) { if (Unbundle) { @@ -1446,7 +257,9 @@ int main(int argc, const char **argv) { "-targets option is invalid for -list")); } - doWork([]() { return ListBundleIDsInFile(InputFileNames.front()); }); + doWork([&]() { return OffloadBundler::ListBundleIDsInFile( + InputFileNames.front(), + BundlerConfig); }); return 0; } @@ -1454,11 +267,13 @@ int main(int argc, const char **argv) { 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 @@ int main(int argc, const char **argv) { "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 @@ int main(int argc, const char **argv) { } ParsedTargets.insert(Target); - auto OffloadInfo = OffloadTargetInfo(Target); + auto OffloadInfo = OffloadTargetInfo(Target, BundlerConfig); bool KindIsValid = OffloadInfo.isOffloadKindValid(); bool TripleIsValid = OffloadInfo.isTripleValid(); @@ -1519,7 +334,7 @@ int main(int argc, const char **argv) { 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 @@ int main(int argc, const char **argv) { // 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; }