Path: blob/main/contrib/llvm-project/clang/lib/Driver/OffloadBundler.cpp
35233 views
//===- OffloadBundler.cpp - File Bundling and Unbundling ------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file implements an offload bundling API that bundles different files10/// that relate with the same source code but different targets into a single11/// one. Also the implements the opposite functionality, i.e. unbundle files12/// previous created by this API.13///14//===----------------------------------------------------------------------===//1516#include "clang/Driver/OffloadBundler.h"17#include "clang/Basic/Cuda.h"18#include "clang/Basic/TargetID.h"19#include "clang/Basic/Version.h"20#include "llvm/ADT/ArrayRef.h"21#include "llvm/ADT/SmallString.h"22#include "llvm/ADT/SmallVector.h"23#include "llvm/ADT/StringExtras.h"24#include "llvm/ADT/StringMap.h"25#include "llvm/ADT/StringRef.h"26#include "llvm/BinaryFormat/Magic.h"27#include "llvm/Object/Archive.h"28#include "llvm/Object/ArchiveWriter.h"29#include "llvm/Object/Binary.h"30#include "llvm/Object/ObjectFile.h"31#include "llvm/Support/Casting.h"32#include "llvm/Support/Compression.h"33#include "llvm/Support/Debug.h"34#include "llvm/Support/EndianStream.h"35#include "llvm/Support/Errc.h"36#include "llvm/Support/Error.h"37#include "llvm/Support/ErrorOr.h"38#include "llvm/Support/FileSystem.h"39#include "llvm/Support/MD5.h"40#include "llvm/Support/MemoryBuffer.h"41#include "llvm/Support/Path.h"42#include "llvm/Support/Program.h"43#include "llvm/Support/Signals.h"44#include "llvm/Support/StringSaver.h"45#include "llvm/Support/Timer.h"46#include "llvm/Support/WithColor.h"47#include "llvm/Support/raw_ostream.h"48#include "llvm/TargetParser/Host.h"49#include "llvm/TargetParser/Triple.h"50#include <algorithm>51#include <cassert>52#include <cstddef>53#include <cstdint>54#include <forward_list>55#include <llvm/Support/Process.h>56#include <memory>57#include <set>58#include <string>59#include <system_error>60#include <utility>6162using namespace llvm;63using namespace llvm::object;64using namespace clang;6566static llvm::TimerGroup67ClangOffloadBundlerTimerGroup("Clang Offload Bundler Timer Group",68"Timer group for clang offload bundler");6970/// Magic string that marks the existence of offloading data.71#define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__"7273OffloadTargetInfo::OffloadTargetInfo(const StringRef Target,74const OffloadBundlerConfig &BC)75: BundlerConfig(BC) {7677// TODO: Add error checking from ClangOffloadBundler.cpp78auto TargetFeatures = Target.split(':');79auto TripleOrGPU = TargetFeatures.first.rsplit('-');8081if (clang::StringToOffloadArch(TripleOrGPU.second) !=82clang::OffloadArch::UNKNOWN) {83auto KindTriple = TripleOrGPU.first.split('-');84this->OffloadKind = KindTriple.first;8586// Enforce optional env field to standardize bundles87llvm::Triple t = llvm::Triple(KindTriple.second);88this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(),89t.getOSName(), t.getEnvironmentName());9091this->TargetID = Target.substr(Target.find(TripleOrGPU.second));92} else {93auto KindTriple = TargetFeatures.first.split('-');94this->OffloadKind = KindTriple.first;9596// Enforce optional env field to standardize bundles97llvm::Triple t = llvm::Triple(KindTriple.second);98this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(),99t.getOSName(), t.getEnvironmentName());100101this->TargetID = "";102}103}104105bool OffloadTargetInfo::hasHostKind() const {106return this->OffloadKind == "host";107}108109bool OffloadTargetInfo::isOffloadKindValid() const {110return OffloadKind == "host" || OffloadKind == "openmp" ||111OffloadKind == "hip" || OffloadKind == "hipv4";112}113114bool OffloadTargetInfo::isOffloadKindCompatible(115const StringRef TargetOffloadKind) const {116if ((OffloadKind == TargetOffloadKind) ||117(OffloadKind == "hip" && TargetOffloadKind == "hipv4") ||118(OffloadKind == "hipv4" && TargetOffloadKind == "hip"))119return true;120121if (BundlerConfig.HipOpenmpCompatible) {122bool HIPCompatibleWithOpenMP = OffloadKind.starts_with_insensitive("hip") &&123TargetOffloadKind == "openmp";124bool OpenMPCompatibleWithHIP =125OffloadKind == "openmp" &&126TargetOffloadKind.starts_with_insensitive("hip");127return HIPCompatibleWithOpenMP || OpenMPCompatibleWithHIP;128}129return false;130}131132bool OffloadTargetInfo::isTripleValid() const {133return !Triple.str().empty() && Triple.getArch() != Triple::UnknownArch;134}135136bool OffloadTargetInfo::operator==(const OffloadTargetInfo &Target) const {137return OffloadKind == Target.OffloadKind &&138Triple.isCompatibleWith(Target.Triple) && TargetID == Target.TargetID;139}140141std::string OffloadTargetInfo::str() const {142return Twine(OffloadKind + "-" + Triple.str() + "-" + TargetID).str();143}144145static StringRef getDeviceFileExtension(StringRef Device,146StringRef BundleFileName) {147if (Device.contains("gfx"))148return ".bc";149if (Device.contains("sm_"))150return ".cubin";151return sys::path::extension(BundleFileName);152}153154static std::string getDeviceLibraryFileName(StringRef BundleFileName,155StringRef Device) {156StringRef LibName = sys::path::stem(BundleFileName);157StringRef Extension = getDeviceFileExtension(Device, BundleFileName);158159std::string Result;160Result += LibName;161Result += Extension;162return Result;163}164165namespace {166/// Generic file handler interface.167class FileHandler {168public:169struct BundleInfo {170StringRef BundleID;171};172173FileHandler() {}174175virtual ~FileHandler() {}176177/// Update the file handler with information from the header of the bundled178/// file.179virtual Error ReadHeader(MemoryBuffer &Input) = 0;180181/// Read the marker of the next bundled to be read in the file. The bundle182/// name is returned if there is one in the file, or `std::nullopt` if there183/// are no more bundles to be read.184virtual Expected<std::optional<StringRef>>185ReadBundleStart(MemoryBuffer &Input) = 0;186187/// Read the marker that closes the current bundle.188virtual Error ReadBundleEnd(MemoryBuffer &Input) = 0;189190/// Read the current bundle and write the result into the stream \a OS.191virtual Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) = 0;192193/// Write the header of the bundled file to \a OS based on the information194/// gathered from \a Inputs.195virtual Error WriteHeader(raw_ostream &OS,196ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) = 0;197198/// Write the marker that initiates a bundle for the triple \a TargetTriple to199/// \a OS.200virtual Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) = 0;201202/// Write the marker that closes a bundle for the triple \a TargetTriple to \a203/// OS.204virtual Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) = 0;205206/// Write the bundle from \a Input into \a OS.207virtual Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) = 0;208209/// Finalize output file.210virtual Error finalizeOutputFile() { return Error::success(); }211212/// List bundle IDs in \a Input.213virtual Error listBundleIDs(MemoryBuffer &Input) {214if (Error Err = ReadHeader(Input))215return Err;216return forEachBundle(Input, [&](const BundleInfo &Info) -> Error {217llvm::outs() << Info.BundleID << '\n';218Error Err = listBundleIDsCallback(Input, Info);219if (Err)220return Err;221return Error::success();222});223}224225/// Get bundle IDs in \a Input in \a BundleIds.226virtual Error getBundleIDs(MemoryBuffer &Input,227std::set<StringRef> &BundleIds) {228if (Error Err = ReadHeader(Input))229return Err;230return forEachBundle(Input, [&](const BundleInfo &Info) -> Error {231BundleIds.insert(Info.BundleID);232Error Err = listBundleIDsCallback(Input, Info);233if (Err)234return Err;235return Error::success();236});237}238239/// For each bundle in \a Input, do \a Func.240Error forEachBundle(MemoryBuffer &Input,241std::function<Error(const BundleInfo &)> Func) {242while (true) {243Expected<std::optional<StringRef>> CurTripleOrErr =244ReadBundleStart(Input);245if (!CurTripleOrErr)246return CurTripleOrErr.takeError();247248// No more bundles.249if (!*CurTripleOrErr)250break;251252StringRef CurTriple = **CurTripleOrErr;253assert(!CurTriple.empty());254255BundleInfo Info{CurTriple};256if (Error Err = Func(Info))257return Err;258}259return Error::success();260}261262protected:263virtual Error listBundleIDsCallback(MemoryBuffer &Input,264const BundleInfo &Info) {265return Error::success();266}267};268269/// Handler for binary files. The bundled file will have the following format270/// (all integers are stored in little-endian format):271///272/// "OFFLOAD_BUNDLER_MAGIC_STR" (ASCII encoding of the string)273///274/// NumberOfOffloadBundles (8-byte integer)275///276/// OffsetOfBundle1 (8-byte integer)277/// SizeOfBundle1 (8-byte integer)278/// NumberOfBytesInTripleOfBundle1 (8-byte integer)279/// TripleOfBundle1 (byte length defined before)280///281/// ...282///283/// OffsetOfBundleN (8-byte integer)284/// SizeOfBundleN (8-byte integer)285/// NumberOfBytesInTripleOfBundleN (8-byte integer)286/// TripleOfBundleN (byte length defined before)287///288/// Bundle1289/// ...290/// BundleN291292/// Read 8-byte integers from a buffer in little-endian format.293static uint64_t Read8byteIntegerFromBuffer(StringRef Buffer, size_t pos) {294return llvm::support::endian::read64le(Buffer.data() + pos);295}296297/// Write 8-byte integers to a buffer in little-endian format.298static void Write8byteIntegerToBuffer(raw_ostream &OS, uint64_t Val) {299llvm::support::endian::write(OS, Val, llvm::endianness::little);300}301302class BinaryFileHandler final : public FileHandler {303/// Information about the bundles extracted from the header.304struct BinaryBundleInfo final : public BundleInfo {305/// Size of the bundle.306uint64_t Size = 0u;307/// Offset at which the bundle starts in the bundled file.308uint64_t Offset = 0u;309310BinaryBundleInfo() {}311BinaryBundleInfo(uint64_t Size, uint64_t Offset)312: Size(Size), Offset(Offset) {}313};314315/// Map between a triple and the corresponding bundle information.316StringMap<BinaryBundleInfo> BundlesInfo;317318/// Iterator for the bundle information that is being read.319StringMap<BinaryBundleInfo>::iterator CurBundleInfo;320StringMap<BinaryBundleInfo>::iterator NextBundleInfo;321322/// Current bundle target to be written.323std::string CurWriteBundleTarget;324325/// Configuration options and arrays for this bundler job326const OffloadBundlerConfig &BundlerConfig;327328public:329// TODO: Add error checking from ClangOffloadBundler.cpp330BinaryFileHandler(const OffloadBundlerConfig &BC) : BundlerConfig(BC) {}331332~BinaryFileHandler() final {}333334Error ReadHeader(MemoryBuffer &Input) final {335StringRef FC = Input.getBuffer();336337// Initialize the current bundle with the end of the container.338CurBundleInfo = BundlesInfo.end();339340// Check if buffer is smaller than magic string.341size_t ReadChars = sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1;342if (ReadChars > FC.size())343return Error::success();344345// Check if no magic was found.346if (llvm::identify_magic(FC) != llvm::file_magic::offload_bundle)347return Error::success();348349// Read number of bundles.350if (ReadChars + 8 > FC.size())351return Error::success();352353uint64_t NumberOfBundles = Read8byteIntegerFromBuffer(FC, ReadChars);354ReadChars += 8;355356// Read bundle offsets, sizes and triples.357for (uint64_t i = 0; i < NumberOfBundles; ++i) {358359// Read offset.360if (ReadChars + 8 > FC.size())361return Error::success();362363uint64_t Offset = Read8byteIntegerFromBuffer(FC, ReadChars);364ReadChars += 8;365366// Read size.367if (ReadChars + 8 > FC.size())368return Error::success();369370uint64_t Size = Read8byteIntegerFromBuffer(FC, ReadChars);371ReadChars += 8;372373// Read triple size.374if (ReadChars + 8 > FC.size())375return Error::success();376377uint64_t TripleSize = Read8byteIntegerFromBuffer(FC, ReadChars);378ReadChars += 8;379380// Read triple.381if (ReadChars + TripleSize > FC.size())382return Error::success();383384StringRef Triple(&FC.data()[ReadChars], TripleSize);385ReadChars += TripleSize;386387// Check if the offset and size make sense.388if (!Offset || Offset + Size > FC.size())389return Error::success();390391assert(!BundlesInfo.contains(Triple) && "Triple is duplicated??");392BundlesInfo[Triple] = BinaryBundleInfo(Size, Offset);393}394// Set the iterator to where we will start to read.395CurBundleInfo = BundlesInfo.end();396NextBundleInfo = BundlesInfo.begin();397return Error::success();398}399400Expected<std::optional<StringRef>>401ReadBundleStart(MemoryBuffer &Input) final {402if (NextBundleInfo == BundlesInfo.end())403return std::nullopt;404CurBundleInfo = NextBundleInfo++;405return CurBundleInfo->first();406}407408Error ReadBundleEnd(MemoryBuffer &Input) final {409assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!");410return Error::success();411}412413Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final {414assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!");415StringRef FC = Input.getBuffer();416OS.write(FC.data() + CurBundleInfo->second.Offset,417CurBundleInfo->second.Size);418return Error::success();419}420421Error WriteHeader(raw_ostream &OS,422ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {423424// Compute size of the header.425uint64_t HeaderSize = 0;426427HeaderSize += sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1;428HeaderSize += 8; // Number of Bundles429430for (auto &T : BundlerConfig.TargetNames) {431HeaderSize += 3 * 8; // Bundle offset, Size of bundle and size of triple.432HeaderSize += T.size(); // The triple.433}434435// Write to the buffer the header.436OS << OFFLOAD_BUNDLER_MAGIC_STR;437438Write8byteIntegerToBuffer(OS, BundlerConfig.TargetNames.size());439440unsigned Idx = 0;441for (auto &T : BundlerConfig.TargetNames) {442MemoryBuffer &MB = *Inputs[Idx++];443HeaderSize = alignTo(HeaderSize, BundlerConfig.BundleAlignment);444// Bundle offset.445Write8byteIntegerToBuffer(OS, HeaderSize);446// Size of the bundle (adds to the next bundle's offset)447Write8byteIntegerToBuffer(OS, MB.getBufferSize());448BundlesInfo[T] = BinaryBundleInfo(MB.getBufferSize(), HeaderSize);449HeaderSize += MB.getBufferSize();450// Size of the triple451Write8byteIntegerToBuffer(OS, T.size());452// Triple453OS << T;454}455return Error::success();456}457458Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {459CurWriteBundleTarget = TargetTriple.str();460return Error::success();461}462463Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {464return Error::success();465}466467Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final {468auto BI = BundlesInfo[CurWriteBundleTarget];469470// Pad with 0 to reach specified offset.471size_t CurrentPos = OS.tell();472size_t PaddingSize = BI.Offset > CurrentPos ? BI.Offset - CurrentPos : 0;473for (size_t I = 0; I < PaddingSize; ++I)474OS.write('\0');475assert(OS.tell() == BI.Offset);476477OS.write(Input.getBufferStart(), Input.getBufferSize());478479return Error::success();480}481};482483// This class implements a list of temporary files that are removed upon484// object destruction.485class TempFileHandlerRAII {486public:487~TempFileHandlerRAII() {488for (const auto &File : Files)489sys::fs::remove(File);490}491492// Creates temporary file with given contents.493Expected<StringRef> Create(std::optional<ArrayRef<char>> Contents) {494SmallString<128u> File;495if (std::error_code EC =496sys::fs::createTemporaryFile("clang-offload-bundler", "tmp", File))497return createFileError(File, EC);498Files.push_front(File);499500if (Contents) {501std::error_code EC;502raw_fd_ostream OS(File, EC);503if (EC)504return createFileError(File, EC);505OS.write(Contents->data(), Contents->size());506}507return Files.front().str();508}509510private:511std::forward_list<SmallString<128u>> Files;512};513514/// Handler for object files. The bundles are organized by sections with a515/// designated name.516///517/// To unbundle, we just copy the contents of the designated section.518class ObjectFileHandler final : public FileHandler {519520/// The object file we are currently dealing with.521std::unique_ptr<ObjectFile> Obj;522523/// Return the input file contents.524StringRef getInputFileContents() const { return Obj->getData(); }525526/// Return bundle name (<kind>-<triple>) if the provided section is an offload527/// section.528static Expected<std::optional<StringRef>>529IsOffloadSection(SectionRef CurSection) {530Expected<StringRef> NameOrErr = CurSection.getName();531if (!NameOrErr)532return NameOrErr.takeError();533534// If it does not start with the reserved suffix, just skip this section.535if (llvm::identify_magic(*NameOrErr) != llvm::file_magic::offload_bundle)536return std::nullopt;537538// Return the triple that is right after the reserved prefix.539return NameOrErr->substr(sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1);540}541542/// Total number of inputs.543unsigned NumberOfInputs = 0;544545/// Total number of processed inputs, i.e, inputs that were already546/// read from the buffers.547unsigned NumberOfProcessedInputs = 0;548549/// Iterator of the current and next section.550section_iterator CurrentSection;551section_iterator NextSection;552553/// Configuration options and arrays for this bundler job554const OffloadBundlerConfig &BundlerConfig;555556public:557// TODO: Add error checking from ClangOffloadBundler.cpp558ObjectFileHandler(std::unique_ptr<ObjectFile> ObjIn,559const OffloadBundlerConfig &BC)560: Obj(std::move(ObjIn)), CurrentSection(Obj->section_begin()),561NextSection(Obj->section_begin()), BundlerConfig(BC) {}562563~ObjectFileHandler() final {}564565Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); }566567Expected<std::optional<StringRef>>568ReadBundleStart(MemoryBuffer &Input) final {569while (NextSection != Obj->section_end()) {570CurrentSection = NextSection;571++NextSection;572573// Check if the current section name starts with the reserved prefix. If574// so, return the triple.575Expected<std::optional<StringRef>> TripleOrErr =576IsOffloadSection(*CurrentSection);577if (!TripleOrErr)578return TripleOrErr.takeError();579if (*TripleOrErr)580return **TripleOrErr;581}582return std::nullopt;583}584585Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); }586587Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final {588Expected<StringRef> ContentOrErr = CurrentSection->getContents();589if (!ContentOrErr)590return ContentOrErr.takeError();591StringRef Content = *ContentOrErr;592593// Copy fat object contents to the output when extracting host bundle.594std::string ModifiedContent;595if (Content.size() == 1u && Content.front() == 0) {596auto HostBundleOrErr = getHostBundle(597StringRef(Input.getBufferStart(), Input.getBufferSize()));598if (!HostBundleOrErr)599return HostBundleOrErr.takeError();600601ModifiedContent = std::move(*HostBundleOrErr);602Content = ModifiedContent;603}604605OS.write(Content.data(), Content.size());606return Error::success();607}608609Error WriteHeader(raw_ostream &OS,610ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {611assert(BundlerConfig.HostInputIndex != ~0u &&612"Host input index not defined.");613614// Record number of inputs.615NumberOfInputs = Inputs.size();616return Error::success();617}618619Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {620++NumberOfProcessedInputs;621return Error::success();622}623624Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {625return Error::success();626}627628Error finalizeOutputFile() final {629assert(NumberOfProcessedInputs <= NumberOfInputs &&630"Processing more inputs that actually exist!");631assert(BundlerConfig.HostInputIndex != ~0u &&632"Host input index not defined.");633634// If this is not the last output, we don't have to do anything.635if (NumberOfProcessedInputs != NumberOfInputs)636return Error::success();637638// We will use llvm-objcopy to add target objects sections to the output639// fat object. These sections should have 'exclude' flag set which tells640// link editor to remove them from linker inputs when linking executable or641// shared library.642643assert(BundlerConfig.ObjcopyPath != "" &&644"llvm-objcopy path not specified");645646// Temporary files that need to be removed.647TempFileHandlerRAII TempFiles;648649// Compose llvm-objcopy command line for add target objects' sections with650// appropriate flags.651BumpPtrAllocator Alloc;652StringSaver SS{Alloc};653SmallVector<StringRef, 8u> ObjcopyArgs{"llvm-objcopy"};654655for (unsigned I = 0; I < NumberOfInputs; ++I) {656StringRef InputFile = BundlerConfig.InputFileNames[I];657if (I == BundlerConfig.HostInputIndex) {658// Special handling for the host bundle. We do not need to add a659// standard bundle for the host object since we are going to use fat660// object as a host object. Therefore use dummy contents (one zero byte)661// when creating section for the host bundle.662Expected<StringRef> TempFileOrErr = TempFiles.Create(ArrayRef<char>(0));663if (!TempFileOrErr)664return TempFileOrErr.takeError();665InputFile = *TempFileOrErr;666}667668ObjcopyArgs.push_back(669SS.save(Twine("--add-section=") + OFFLOAD_BUNDLER_MAGIC_STR +670BundlerConfig.TargetNames[I] + "=" + InputFile));671ObjcopyArgs.push_back(672SS.save(Twine("--set-section-flags=") + OFFLOAD_BUNDLER_MAGIC_STR +673BundlerConfig.TargetNames[I] + "=readonly,exclude"));674}675ObjcopyArgs.push_back("--");676ObjcopyArgs.push_back(677BundlerConfig.InputFileNames[BundlerConfig.HostInputIndex]);678ObjcopyArgs.push_back(BundlerConfig.OutputFileNames.front());679680if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs))681return Err;682683return Error::success();684}685686Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final {687return Error::success();688}689690private:691Error executeObjcopy(StringRef Objcopy, ArrayRef<StringRef> Args) {692// If the user asked for the commands to be printed out, we do that693// instead of executing it.694if (BundlerConfig.PrintExternalCommands) {695errs() << "\"" << Objcopy << "\"";696for (StringRef Arg : drop_begin(Args, 1))697errs() << " \"" << Arg << "\"";698errs() << "\n";699} else {700if (sys::ExecuteAndWait(Objcopy, Args))701return createStringError(inconvertibleErrorCode(),702"'llvm-objcopy' tool failed");703}704return Error::success();705}706707Expected<std::string> getHostBundle(StringRef Input) {708TempFileHandlerRAII TempFiles;709710auto ModifiedObjPathOrErr = TempFiles.Create(std::nullopt);711if (!ModifiedObjPathOrErr)712return ModifiedObjPathOrErr.takeError();713StringRef ModifiedObjPath = *ModifiedObjPathOrErr;714715BumpPtrAllocator Alloc;716StringSaver SS{Alloc};717SmallVector<StringRef, 16> ObjcopyArgs{"llvm-objcopy"};718719ObjcopyArgs.push_back("--regex");720ObjcopyArgs.push_back("--remove-section=__CLANG_OFFLOAD_BUNDLE__.*");721ObjcopyArgs.push_back("--");722723StringRef ObjcopyInputFileName;724// When unbundling an archive, the content of each object file in the725// archive is passed to this function by parameter Input, which is different726// from the content of the original input archive file, therefore it needs727// to be saved to a temporary file before passed to llvm-objcopy. Otherwise,728// Input is the same as the content of the original input file, therefore729// temporary file is not needed.730if (StringRef(BundlerConfig.FilesType).starts_with("a")) {731auto InputFileOrErr =732TempFiles.Create(ArrayRef<char>(Input.data(), Input.size()));733if (!InputFileOrErr)734return InputFileOrErr.takeError();735ObjcopyInputFileName = *InputFileOrErr;736} else737ObjcopyInputFileName = BundlerConfig.InputFileNames.front();738739ObjcopyArgs.push_back(ObjcopyInputFileName);740ObjcopyArgs.push_back(ModifiedObjPath);741742if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs))743return std::move(Err);744745auto BufOrErr = MemoryBuffer::getFile(ModifiedObjPath);746if (!BufOrErr)747return createStringError(BufOrErr.getError(),748"Failed to read back the modified object file");749750return BufOrErr->get()->getBuffer().str();751}752};753754/// Handler for text files. The bundled file will have the following format.755///756/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple"757/// Bundle 1758/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple"759/// ...760/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple"761/// Bundle N762/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple"763class TextFileHandler final : public FileHandler {764/// String that begins a line comment.765StringRef Comment;766767/// String that initiates a bundle.768std::string BundleStartString;769770/// String that closes a bundle.771std::string BundleEndString;772773/// Number of chars read from input.774size_t ReadChars = 0u;775776protected:777Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); }778779Expected<std::optional<StringRef>>780ReadBundleStart(MemoryBuffer &Input) final {781StringRef FC = Input.getBuffer();782783// Find start of the bundle.784ReadChars = FC.find(BundleStartString, ReadChars);785if (ReadChars == FC.npos)786return std::nullopt;787788// Get position of the triple.789size_t TripleStart = ReadChars = ReadChars + BundleStartString.size();790791// Get position that closes the triple.792size_t TripleEnd = ReadChars = FC.find("\n", ReadChars);793if (TripleEnd == FC.npos)794return std::nullopt;795796// Next time we read after the new line.797++ReadChars;798799return StringRef(&FC.data()[TripleStart], TripleEnd - TripleStart);800}801802Error ReadBundleEnd(MemoryBuffer &Input) final {803StringRef FC = Input.getBuffer();804805// Read up to the next new line.806assert(FC[ReadChars] == '\n' && "The bundle should end with a new line.");807808size_t TripleEnd = ReadChars = FC.find("\n", ReadChars + 1);809if (TripleEnd != FC.npos)810// Next time we read after the new line.811++ReadChars;812813return Error::success();814}815816Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final {817StringRef FC = Input.getBuffer();818size_t BundleStart = ReadChars;819820// Find end of the bundle.821size_t BundleEnd = ReadChars = FC.find(BundleEndString, ReadChars);822823StringRef Bundle(&FC.data()[BundleStart], BundleEnd - BundleStart);824OS << Bundle;825826return Error::success();827}828829Error WriteHeader(raw_ostream &OS,830ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {831return Error::success();832}833834Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {835OS << BundleStartString << TargetTriple << "\n";836return Error::success();837}838839Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {840OS << BundleEndString << TargetTriple << "\n";841return Error::success();842}843844Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final {845OS << Input.getBuffer();846return Error::success();847}848849public:850TextFileHandler(StringRef Comment) : Comment(Comment), ReadChars(0) {851BundleStartString =852"\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__START__ ";853BundleEndString =854"\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__END__ ";855}856857Error listBundleIDsCallback(MemoryBuffer &Input,858const BundleInfo &Info) final {859// TODO: To list bundle IDs in a bundled text file we need to go through860// all bundles. The format of bundled text file may need to include a861// header if the performance of listing bundle IDs of bundled text file is862// important.863ReadChars = Input.getBuffer().find(BundleEndString, ReadChars);864if (Error Err = ReadBundleEnd(Input))865return Err;866return Error::success();867}868};869} // namespace870871/// Return an appropriate object file handler. We use the specific object872/// handler if we know how to deal with that format, otherwise we use a default873/// binary file handler.874static std::unique_ptr<FileHandler>875CreateObjectFileHandler(MemoryBuffer &FirstInput,876const OffloadBundlerConfig &BundlerConfig) {877// Check if the input file format is one that we know how to deal with.878Expected<std::unique_ptr<Binary>> BinaryOrErr = createBinary(FirstInput);879880// We only support regular object files. If failed to open the input as a881// known binary or this is not an object file use the default binary handler.882if (errorToBool(BinaryOrErr.takeError()) || !isa<ObjectFile>(*BinaryOrErr))883return std::make_unique<BinaryFileHandler>(BundlerConfig);884885// Otherwise create an object file handler. The handler will be owned by the886// client of this function.887return std::make_unique<ObjectFileHandler>(888std::unique_ptr<ObjectFile>(cast<ObjectFile>(BinaryOrErr->release())),889BundlerConfig);890}891892/// Return an appropriate handler given the input files and options.893static Expected<std::unique_ptr<FileHandler>>894CreateFileHandler(MemoryBuffer &FirstInput,895const OffloadBundlerConfig &BundlerConfig) {896std::string FilesType = BundlerConfig.FilesType;897898if (FilesType == "i")899return std::make_unique<TextFileHandler>(/*Comment=*/"//");900if (FilesType == "ii")901return std::make_unique<TextFileHandler>(/*Comment=*/"//");902if (FilesType == "cui")903return std::make_unique<TextFileHandler>(/*Comment=*/"//");904if (FilesType == "hipi")905return std::make_unique<TextFileHandler>(/*Comment=*/"//");906// TODO: `.d` should be eventually removed once `-M` and its variants are907// handled properly in offload compilation.908if (FilesType == "d")909return std::make_unique<TextFileHandler>(/*Comment=*/"#");910if (FilesType == "ll")911return std::make_unique<TextFileHandler>(/*Comment=*/";");912if (FilesType == "bc")913return std::make_unique<BinaryFileHandler>(BundlerConfig);914if (FilesType == "s")915return std::make_unique<TextFileHandler>(/*Comment=*/"#");916if (FilesType == "o")917return CreateObjectFileHandler(FirstInput, BundlerConfig);918if (FilesType == "a")919return CreateObjectFileHandler(FirstInput, BundlerConfig);920if (FilesType == "gch")921return std::make_unique<BinaryFileHandler>(BundlerConfig);922if (FilesType == "ast")923return std::make_unique<BinaryFileHandler>(BundlerConfig);924925return createStringError(errc::invalid_argument,926"'" + FilesType + "': invalid file type specified");927}928929OffloadBundlerConfig::OffloadBundlerConfig() {930if (llvm::compression::zstd::isAvailable()) {931CompressionFormat = llvm::compression::Format::Zstd;932// Compression level 3 is usually sufficient for zstd since long distance933// matching is enabled.934CompressionLevel = 3;935} else if (llvm::compression::zlib::isAvailable()) {936CompressionFormat = llvm::compression::Format::Zlib;937// Use default level for zlib since higher level does not have significant938// improvement.939CompressionLevel = llvm::compression::zlib::DefaultCompression;940}941auto IgnoreEnvVarOpt =942llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_IGNORE_ENV_VAR");943if (IgnoreEnvVarOpt.has_value() && IgnoreEnvVarOpt.value() == "1")944return;945946auto VerboseEnvVarOpt = llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_VERBOSE");947if (VerboseEnvVarOpt.has_value())948Verbose = VerboseEnvVarOpt.value() == "1";949950auto CompressEnvVarOpt =951llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESS");952if (CompressEnvVarOpt.has_value())953Compress = CompressEnvVarOpt.value() == "1";954955auto CompressionLevelEnvVarOpt =956llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESSION_LEVEL");957if (CompressionLevelEnvVarOpt.has_value()) {958llvm::StringRef CompressionLevelStr = CompressionLevelEnvVarOpt.value();959int Level;960if (!CompressionLevelStr.getAsInteger(10, Level))961CompressionLevel = Level;962else963llvm::errs()964<< "Warning: Invalid value for OFFLOAD_BUNDLER_COMPRESSION_LEVEL: "965<< CompressionLevelStr.str() << ". Ignoring it.\n";966}967}968969// Utility function to format numbers with commas970static std::string formatWithCommas(unsigned long long Value) {971std::string Num = std::to_string(Value);972int InsertPosition = Num.length() - 3;973while (InsertPosition > 0) {974Num.insert(InsertPosition, ",");975InsertPosition -= 3;976}977return Num;978}979980llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>981CompressedOffloadBundle::compress(llvm::compression::Params P,982const llvm::MemoryBuffer &Input,983bool Verbose) {984if (!llvm::compression::zstd::isAvailable() &&985!llvm::compression::zlib::isAvailable())986return createStringError(llvm::inconvertibleErrorCode(),987"Compression not supported");988989llvm::Timer HashTimer("Hash Calculation Timer", "Hash calculation time",990ClangOffloadBundlerTimerGroup);991if (Verbose)992HashTimer.startTimer();993llvm::MD5 Hash;994llvm::MD5::MD5Result Result;995Hash.update(Input.getBuffer());996Hash.final(Result);997uint64_t TruncatedHash = Result.low();998if (Verbose)999HashTimer.stopTimer();10001001SmallVector<uint8_t, 0> CompressedBuffer;1002auto BufferUint8 = llvm::ArrayRef<uint8_t>(1003reinterpret_cast<const uint8_t *>(Input.getBuffer().data()),1004Input.getBuffer().size());10051006llvm::Timer CompressTimer("Compression Timer", "Compression time",1007ClangOffloadBundlerTimerGroup);1008if (Verbose)1009CompressTimer.startTimer();1010llvm::compression::compress(P, BufferUint8, CompressedBuffer);1011if (Verbose)1012CompressTimer.stopTimer();10131014uint16_t CompressionMethod = static_cast<uint16_t>(P.format);1015uint32_t UncompressedSize = Input.getBuffer().size();1016uint32_t TotalFileSize = MagicNumber.size() + sizeof(TotalFileSize) +1017sizeof(Version) + sizeof(CompressionMethod) +1018sizeof(UncompressedSize) + sizeof(TruncatedHash) +1019CompressedBuffer.size();10201021SmallVector<char, 0> FinalBuffer;1022llvm::raw_svector_ostream OS(FinalBuffer);1023OS << MagicNumber;1024OS.write(reinterpret_cast<const char *>(&Version), sizeof(Version));1025OS.write(reinterpret_cast<const char *>(&CompressionMethod),1026sizeof(CompressionMethod));1027OS.write(reinterpret_cast<const char *>(&TotalFileSize),1028sizeof(TotalFileSize));1029OS.write(reinterpret_cast<const char *>(&UncompressedSize),1030sizeof(UncompressedSize));1031OS.write(reinterpret_cast<const char *>(&TruncatedHash),1032sizeof(TruncatedHash));1033OS.write(reinterpret_cast<const char *>(CompressedBuffer.data()),1034CompressedBuffer.size());10351036if (Verbose) {1037auto MethodUsed =1038P.format == llvm::compression::Format::Zstd ? "zstd" : "zlib";1039double CompressionRate =1040static_cast<double>(UncompressedSize) / CompressedBuffer.size();1041double CompressionTimeSeconds = CompressTimer.getTotalTime().getWallTime();1042double CompressionSpeedMBs =1043(UncompressedSize / (1024.0 * 1024.0)) / CompressionTimeSeconds;10441045llvm::errs() << "Compressed bundle format version: " << Version << "\n"1046<< "Total file size (including headers): "1047<< formatWithCommas(TotalFileSize) << " bytes\n"1048<< "Compression method used: " << MethodUsed << "\n"1049<< "Compression level: " << P.level << "\n"1050<< "Binary size before compression: "1051<< formatWithCommas(UncompressedSize) << " bytes\n"1052<< "Binary size after compression: "1053<< formatWithCommas(CompressedBuffer.size()) << " bytes\n"1054<< "Compression rate: "1055<< llvm::format("%.2lf", CompressionRate) << "\n"1056<< "Compression ratio: "1057<< llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"1058<< "Compression speed: "1059<< llvm::format("%.2lf MB/s", CompressionSpeedMBs) << "\n"1060<< "Truncated MD5 hash: "1061<< llvm::format_hex(TruncatedHash, 16) << "\n";1062}1063return llvm::MemoryBuffer::getMemBufferCopy(1064llvm::StringRef(FinalBuffer.data(), FinalBuffer.size()));1065}10661067llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>1068CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input,1069bool Verbose) {10701071StringRef Blob = Input.getBuffer();10721073if (Blob.size() < V1HeaderSize)1074return llvm::MemoryBuffer::getMemBufferCopy(Blob);10751076if (llvm::identify_magic(Blob) !=1077llvm::file_magic::offload_bundle_compressed) {1078if (Verbose)1079llvm::errs() << "Uncompressed bundle.\n";1080return llvm::MemoryBuffer::getMemBufferCopy(Blob);1081}10821083size_t CurrentOffset = MagicSize;10841085uint16_t ThisVersion;1086memcpy(&ThisVersion, Blob.data() + CurrentOffset, sizeof(uint16_t));1087CurrentOffset += VersionFieldSize;10881089uint16_t CompressionMethod;1090memcpy(&CompressionMethod, Blob.data() + CurrentOffset, sizeof(uint16_t));1091CurrentOffset += MethodFieldSize;10921093uint32_t TotalFileSize;1094if (ThisVersion >= 2) {1095if (Blob.size() < V2HeaderSize)1096return createStringError(inconvertibleErrorCode(),1097"Compressed bundle header size too small");1098memcpy(&TotalFileSize, Blob.data() + CurrentOffset, sizeof(uint32_t));1099CurrentOffset += FileSizeFieldSize;1100}11011102uint32_t UncompressedSize;1103memcpy(&UncompressedSize, Blob.data() + CurrentOffset, sizeof(uint32_t));1104CurrentOffset += UncompressedSizeFieldSize;11051106uint64_t StoredHash;1107memcpy(&StoredHash, Blob.data() + CurrentOffset, sizeof(uint64_t));1108CurrentOffset += HashFieldSize;11091110llvm::compression::Format CompressionFormat;1111if (CompressionMethod ==1112static_cast<uint16_t>(llvm::compression::Format::Zlib))1113CompressionFormat = llvm::compression::Format::Zlib;1114else if (CompressionMethod ==1115static_cast<uint16_t>(llvm::compression::Format::Zstd))1116CompressionFormat = llvm::compression::Format::Zstd;1117else1118return createStringError(inconvertibleErrorCode(),1119"Unknown compressing method");11201121llvm::Timer DecompressTimer("Decompression Timer", "Decompression time",1122ClangOffloadBundlerTimerGroup);1123if (Verbose)1124DecompressTimer.startTimer();11251126SmallVector<uint8_t, 0> DecompressedData;1127StringRef CompressedData = Blob.substr(CurrentOffset);1128if (llvm::Error DecompressionError = llvm::compression::decompress(1129CompressionFormat, llvm::arrayRefFromStringRef(CompressedData),1130DecompressedData, UncompressedSize))1131return createStringError(inconvertibleErrorCode(),1132"Could not decompress embedded file contents: " +1133llvm::toString(std::move(DecompressionError)));11341135if (Verbose) {1136DecompressTimer.stopTimer();11371138double DecompressionTimeSeconds =1139DecompressTimer.getTotalTime().getWallTime();11401141// Recalculate MD5 hash for integrity check1142llvm::Timer HashRecalcTimer("Hash Recalculation Timer",1143"Hash recalculation time",1144ClangOffloadBundlerTimerGroup);1145HashRecalcTimer.startTimer();1146llvm::MD5 Hash;1147llvm::MD5::MD5Result Result;1148Hash.update(llvm::ArrayRef<uint8_t>(DecompressedData.data(),1149DecompressedData.size()));1150Hash.final(Result);1151uint64_t RecalculatedHash = Result.low();1152HashRecalcTimer.stopTimer();1153bool HashMatch = (StoredHash == RecalculatedHash);11541155double CompressionRate =1156static_cast<double>(UncompressedSize) / CompressedData.size();1157double DecompressionSpeedMBs =1158(UncompressedSize / (1024.0 * 1024.0)) / DecompressionTimeSeconds;11591160llvm::errs() << "Compressed bundle format version: " << ThisVersion << "\n";1161if (ThisVersion >= 2)1162llvm::errs() << "Total file size (from header): "1163<< formatWithCommas(TotalFileSize) << " bytes\n";1164llvm::errs() << "Decompression method: "1165<< (CompressionFormat == llvm::compression::Format::Zlib1166? "zlib"1167: "zstd")1168<< "\n"1169<< "Size before decompression: "1170<< formatWithCommas(CompressedData.size()) << " bytes\n"1171<< "Size after decompression: "1172<< formatWithCommas(UncompressedSize) << " bytes\n"1173<< "Compression rate: "1174<< llvm::format("%.2lf", CompressionRate) << "\n"1175<< "Compression ratio: "1176<< llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"1177<< "Decompression speed: "1178<< llvm::format("%.2lf MB/s", DecompressionSpeedMBs) << "\n"1179<< "Stored hash: " << llvm::format_hex(StoredHash, 16) << "\n"1180<< "Recalculated hash: "1181<< llvm::format_hex(RecalculatedHash, 16) << "\n"1182<< "Hashes match: " << (HashMatch ? "Yes" : "No") << "\n";1183}11841185return llvm::MemoryBuffer::getMemBufferCopy(1186llvm::toStringRef(DecompressedData));1187}11881189// List bundle IDs. Return true if an error was found.1190Error OffloadBundler::ListBundleIDsInFile(1191StringRef InputFileName, const OffloadBundlerConfig &BundlerConfig) {1192// Open Input file.1193ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1194MemoryBuffer::getFileOrSTDIN(InputFileName);1195if (std::error_code EC = CodeOrErr.getError())1196return createFileError(InputFileName, EC);11971198// Decompress the input if necessary.1199Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1200CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);1201if (!DecompressedBufferOrErr)1202return createStringError(1203inconvertibleErrorCode(),1204"Failed to decompress input: " +1205llvm::toString(DecompressedBufferOrErr.takeError()));12061207MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr;12081209// Select the right files handler.1210Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1211CreateFileHandler(DecompressedInput, BundlerConfig);1212if (!FileHandlerOrErr)1213return FileHandlerOrErr.takeError();12141215std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1216assert(FH);1217return FH->listBundleIDs(DecompressedInput);1218}12191220/// @brief Checks if a code object \p CodeObjectInfo is compatible with a given1221/// target \p TargetInfo.1222/// @link https://clang.llvm.org/docs/ClangOffloadBundler.html#bundle-entry-id1223bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo,1224const OffloadTargetInfo &TargetInfo) {12251226// Compatible in case of exact match.1227if (CodeObjectInfo == TargetInfo) {1228DEBUG_WITH_TYPE("CodeObjectCompatibility",1229dbgs() << "Compatible: Exact match: \t[CodeObject: "1230<< CodeObjectInfo.str()1231<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");1232return true;1233}12341235// Incompatible if Kinds or Triples mismatch.1236if (!CodeObjectInfo.isOffloadKindCompatible(TargetInfo.OffloadKind) ||1237!CodeObjectInfo.Triple.isCompatibleWith(TargetInfo.Triple)) {1238DEBUG_WITH_TYPE(1239"CodeObjectCompatibility",1240dbgs() << "Incompatible: Kind/Triple mismatch \t[CodeObject: "1241<< CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1242<< "]\n");1243return false;1244}12451246// Incompatible if Processors mismatch.1247llvm::StringMap<bool> CodeObjectFeatureMap, TargetFeatureMap;1248std::optional<StringRef> CodeObjectProc = clang::parseTargetID(1249CodeObjectInfo.Triple, CodeObjectInfo.TargetID, &CodeObjectFeatureMap);1250std::optional<StringRef> TargetProc = clang::parseTargetID(1251TargetInfo.Triple, TargetInfo.TargetID, &TargetFeatureMap);12521253// Both TargetProc and CodeObjectProc can't be empty here.1254if (!TargetProc || !CodeObjectProc ||1255CodeObjectProc.value() != TargetProc.value()) {1256DEBUG_WITH_TYPE("CodeObjectCompatibility",1257dbgs() << "Incompatible: Processor mismatch \t[CodeObject: "1258<< CodeObjectInfo.str()1259<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");1260return false;1261}12621263// Incompatible if CodeObject has more features than Target, irrespective of1264// type or sign of features.1265if (CodeObjectFeatureMap.getNumItems() > TargetFeatureMap.getNumItems()) {1266DEBUG_WITH_TYPE("CodeObjectCompatibility",1267dbgs() << "Incompatible: CodeObject has more features "1268"than target \t[CodeObject: "1269<< CodeObjectInfo.str()1270<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");1271return false;1272}12731274// Compatible if each target feature specified by target is compatible with1275// target feature of code object. The target feature is compatible if the1276// code object does not specify it (meaning Any), or if it specifies it1277// with the same value (meaning On or Off).1278for (const auto &CodeObjectFeature : CodeObjectFeatureMap) {1279auto TargetFeature = TargetFeatureMap.find(CodeObjectFeature.getKey());1280if (TargetFeature == TargetFeatureMap.end()) {1281DEBUG_WITH_TYPE(1282"CodeObjectCompatibility",1283dbgs()1284<< "Incompatible: Value of CodeObject's non-ANY feature is "1285"not matching with Target feature's ANY value \t[CodeObject: "1286<< CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1287<< "]\n");1288return false;1289} else if (TargetFeature->getValue() != CodeObjectFeature.getValue()) {1290DEBUG_WITH_TYPE(1291"CodeObjectCompatibility",1292dbgs() << "Incompatible: Value of CodeObject's non-ANY feature is "1293"not matching with Target feature's non-ANY value "1294"\t[CodeObject: "1295<< CodeObjectInfo.str()1296<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");1297return false;1298}1299}13001301// CodeObject is compatible if all features of Target are:1302// - either, present in the Code Object's features map with the same sign,1303// - or, the feature is missing from CodeObjects's features map i.e. it is1304// set to ANY1305DEBUG_WITH_TYPE(1306"CodeObjectCompatibility",1307dbgs() << "Compatible: Target IDs are compatible \t[CodeObject: "1308<< CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1309<< "]\n");1310return true;1311}13121313/// Bundle the files. Return true if an error was found.1314Error OffloadBundler::BundleFiles() {1315std::error_code EC;13161317// Create a buffer to hold the content before compressing.1318SmallVector<char, 0> Buffer;1319llvm::raw_svector_ostream BufferStream(Buffer);13201321// Open input files.1322SmallVector<std::unique_ptr<MemoryBuffer>, 8u> InputBuffers;1323InputBuffers.reserve(BundlerConfig.InputFileNames.size());1324for (auto &I : BundlerConfig.InputFileNames) {1325ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1326MemoryBuffer::getFileOrSTDIN(I);1327if (std::error_code EC = CodeOrErr.getError())1328return createFileError(I, EC);1329InputBuffers.emplace_back(std::move(*CodeOrErr));1330}13311332// Get the file handler. We use the host buffer as reference.1333assert((BundlerConfig.HostInputIndex != ~0u || BundlerConfig.AllowNoHost) &&1334"Host input index undefined??");1335Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = CreateFileHandler(1336*InputBuffers[BundlerConfig.AllowNoHost ? 01337: BundlerConfig.HostInputIndex],1338BundlerConfig);1339if (!FileHandlerOrErr)1340return FileHandlerOrErr.takeError();13411342std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1343assert(FH);13441345// Write header.1346if (Error Err = FH->WriteHeader(BufferStream, InputBuffers))1347return Err;13481349// Write all bundles along with the start/end markers. If an error was found1350// writing the end of the bundle component, abort the bundle writing.1351auto Input = InputBuffers.begin();1352for (auto &Triple : BundlerConfig.TargetNames) {1353if (Error Err = FH->WriteBundleStart(BufferStream, Triple))1354return Err;1355if (Error Err = FH->WriteBundle(BufferStream, **Input))1356return Err;1357if (Error Err = FH->WriteBundleEnd(BufferStream, Triple))1358return Err;1359++Input;1360}13611362raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), EC,1363sys::fs::OF_None);1364if (EC)1365return createFileError(BundlerConfig.OutputFileNames.front(), EC);13661367SmallVector<char, 0> CompressedBuffer;1368if (BundlerConfig.Compress) {1369std::unique_ptr<llvm::MemoryBuffer> BufferMemory =1370llvm::MemoryBuffer::getMemBufferCopy(1371llvm::StringRef(Buffer.data(), Buffer.size()));1372auto CompressionResult = CompressedOffloadBundle::compress(1373{BundlerConfig.CompressionFormat, BundlerConfig.CompressionLevel,1374/*zstdEnableLdm=*/true},1375*BufferMemory, BundlerConfig.Verbose);1376if (auto Error = CompressionResult.takeError())1377return Error;13781379auto CompressedMemBuffer = std::move(CompressionResult.get());1380CompressedBuffer.assign(CompressedMemBuffer->getBufferStart(),1381CompressedMemBuffer->getBufferEnd());1382} else1383CompressedBuffer = Buffer;13841385OutputFile.write(CompressedBuffer.data(), CompressedBuffer.size());13861387return FH->finalizeOutputFile();1388}13891390// Unbundle the files. Return true if an error was found.1391Error OffloadBundler::UnbundleFiles() {1392// Open Input file.1393ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1394MemoryBuffer::getFileOrSTDIN(BundlerConfig.InputFileNames.front());1395if (std::error_code EC = CodeOrErr.getError())1396return createFileError(BundlerConfig.InputFileNames.front(), EC);13971398// Decompress the input if necessary.1399Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1400CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);1401if (!DecompressedBufferOrErr)1402return createStringError(1403inconvertibleErrorCode(),1404"Failed to decompress input: " +1405llvm::toString(DecompressedBufferOrErr.takeError()));14061407MemoryBuffer &Input = **DecompressedBufferOrErr;14081409// Select the right files handler.1410Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1411CreateFileHandler(Input, BundlerConfig);1412if (!FileHandlerOrErr)1413return FileHandlerOrErr.takeError();14141415std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1416assert(FH);14171418// Read the header of the bundled file.1419if (Error Err = FH->ReadHeader(Input))1420return Err;14211422// Create a work list that consist of the map triple/output file.1423StringMap<StringRef> Worklist;1424auto Output = BundlerConfig.OutputFileNames.begin();1425for (auto &Triple : BundlerConfig.TargetNames) {1426Worklist[Triple] = *Output;1427++Output;1428}14291430// Read all the bundles that are in the work list. If we find no bundles we1431// assume the file is meant for the host target.1432bool FoundHostBundle = false;1433while (!Worklist.empty()) {1434Expected<std::optional<StringRef>> CurTripleOrErr =1435FH->ReadBundleStart(Input);1436if (!CurTripleOrErr)1437return CurTripleOrErr.takeError();14381439// We don't have more bundles.1440if (!*CurTripleOrErr)1441break;14421443StringRef CurTriple = **CurTripleOrErr;1444assert(!CurTriple.empty());14451446auto Output = Worklist.begin();1447for (auto E = Worklist.end(); Output != E; Output++) {1448if (isCodeObjectCompatible(1449OffloadTargetInfo(CurTriple, BundlerConfig),1450OffloadTargetInfo((*Output).first(), BundlerConfig))) {1451break;1452}1453}14541455if (Output == Worklist.end())1456continue;1457// Check if the output file can be opened and copy the bundle to it.1458std::error_code EC;1459raw_fd_ostream OutputFile((*Output).second, EC, sys::fs::OF_None);1460if (EC)1461return createFileError((*Output).second, EC);1462if (Error Err = FH->ReadBundle(OutputFile, Input))1463return Err;1464if (Error Err = FH->ReadBundleEnd(Input))1465return Err;1466Worklist.erase(Output);14671468// Record if we found the host bundle.1469auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig);1470if (OffloadInfo.hasHostKind())1471FoundHostBundle = true;1472}14731474if (!BundlerConfig.AllowMissingBundles && !Worklist.empty()) {1475std::string ErrMsg = "Can't find bundles for";1476std::set<StringRef> Sorted;1477for (auto &E : Worklist)1478Sorted.insert(E.first());1479unsigned I = 0;1480unsigned Last = Sorted.size() - 1;1481for (auto &E : Sorted) {1482if (I != 0 && Last > 1)1483ErrMsg += ",";1484ErrMsg += " ";1485if (I == Last && I != 0)1486ErrMsg += "and ";1487ErrMsg += E.str();1488++I;1489}1490return createStringError(inconvertibleErrorCode(), ErrMsg);1491}14921493// If no bundles were found, assume the input file is the host bundle and1494// create empty files for the remaining targets.1495if (Worklist.size() == BundlerConfig.TargetNames.size()) {1496for (auto &E : Worklist) {1497std::error_code EC;1498raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None);1499if (EC)1500return createFileError(E.second, EC);15011502// If this entry has a host kind, copy the input file to the output file.1503auto OffloadInfo = OffloadTargetInfo(E.getKey(), BundlerConfig);1504if (OffloadInfo.hasHostKind())1505OutputFile.write(Input.getBufferStart(), Input.getBufferSize());1506}1507return Error::success();1508}15091510// If we found elements, we emit an error if none of those were for the host1511// in case host bundle name was provided in command line.1512if (!(FoundHostBundle || BundlerConfig.HostInputIndex == ~0u ||1513BundlerConfig.AllowMissingBundles))1514return createStringError(inconvertibleErrorCode(),1515"Can't find bundle for the host target");15161517// If we still have any elements in the worklist, create empty files for them.1518for (auto &E : Worklist) {1519std::error_code EC;1520raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None);1521if (EC)1522return createFileError(E.second, EC);1523}15241525return Error::success();1526}15271528static Archive::Kind getDefaultArchiveKindForHost() {1529return Triple(sys::getDefaultTargetTriple()).isOSDarwin() ? Archive::K_DARWIN1530: Archive::K_GNU;1531}15321533/// @brief Computes a list of targets among all given targets which are1534/// compatible with this code object1535/// @param [in] CodeObjectInfo Code Object1536/// @param [out] CompatibleTargets List of all compatible targets among all1537/// given targets1538/// @return false, if no compatible target is found.1539static bool1540getCompatibleOffloadTargets(OffloadTargetInfo &CodeObjectInfo,1541SmallVectorImpl<StringRef> &CompatibleTargets,1542const OffloadBundlerConfig &BundlerConfig) {1543if (!CompatibleTargets.empty()) {1544DEBUG_WITH_TYPE("CodeObjectCompatibility",1545dbgs() << "CompatibleTargets list should be empty\n");1546return false;1547}1548for (auto &Target : BundlerConfig.TargetNames) {1549auto TargetInfo = OffloadTargetInfo(Target, BundlerConfig);1550if (isCodeObjectCompatible(CodeObjectInfo, TargetInfo))1551CompatibleTargets.push_back(Target);1552}1553return !CompatibleTargets.empty();1554}15551556// Check that each code object file in the input archive conforms to following1557// rule: for a specific processor, a feature either shows up in all target IDs,1558// or does not show up in any target IDs. Otherwise the target ID combination is1559// invalid.1560static Error1561CheckHeterogeneousArchive(StringRef ArchiveName,1562const OffloadBundlerConfig &BundlerConfig) {1563std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;1564ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =1565MemoryBuffer::getFileOrSTDIN(ArchiveName, true, false);1566if (std::error_code EC = BufOrErr.getError())1567return createFileError(ArchiveName, EC);15681569ArchiveBuffers.push_back(std::move(*BufOrErr));1570Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr =1571Archive::create(ArchiveBuffers.back()->getMemBufferRef());1572if (!LibOrErr)1573return LibOrErr.takeError();15741575auto Archive = std::move(*LibOrErr);15761577Error ArchiveErr = Error::success();1578auto ChildEnd = Archive->child_end();15791580/// Iterate over all bundled code object files in the input archive.1581for (auto ArchiveIter = Archive->child_begin(ArchiveErr);1582ArchiveIter != ChildEnd; ++ArchiveIter) {1583if (ArchiveErr)1584return ArchiveErr;1585auto ArchiveChildNameOrErr = (*ArchiveIter).getName();1586if (!ArchiveChildNameOrErr)1587return ArchiveChildNameOrErr.takeError();15881589auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef();1590if (!CodeObjectBufferRefOrErr)1591return CodeObjectBufferRefOrErr.takeError();15921593auto CodeObjectBuffer =1594MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);15951596Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1597CreateFileHandler(*CodeObjectBuffer, BundlerConfig);1598if (!FileHandlerOrErr)1599return FileHandlerOrErr.takeError();16001601std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;1602assert(FileHandler);16031604std::set<StringRef> BundleIds;1605auto CodeObjectFileError =1606FileHandler->getBundleIDs(*CodeObjectBuffer, BundleIds);1607if (CodeObjectFileError)1608return CodeObjectFileError;16091610auto &&ConflictingArchs = clang::getConflictTargetIDCombination(BundleIds);1611if (ConflictingArchs) {1612std::string ErrMsg =1613Twine("conflicting TargetIDs [" + ConflictingArchs.value().first +1614", " + ConflictingArchs.value().second + "] found in " +1615ArchiveChildNameOrErr.get() + " of " + ArchiveName)1616.str();1617return createStringError(inconvertibleErrorCode(), ErrMsg);1618}1619}16201621return ArchiveErr;1622}16231624/// UnbundleArchive takes an archive file (".a") as input containing bundled1625/// code object files, and a list of offload targets (not host), and extracts1626/// the code objects into a new archive file for each offload target. Each1627/// resulting archive file contains all code object files corresponding to that1628/// particular offload target. The created archive file does not1629/// contain an index of the symbols and code object files are named as1630/// <<Parent Bundle Name>-<CodeObject's TargetID>>, with ':' replaced with '_'.1631Error OffloadBundler::UnbundleArchive() {1632std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;16331634/// Map of target names with list of object files that will form the device1635/// specific archive for that target1636StringMap<std::vector<NewArchiveMember>> OutputArchivesMap;16371638// Map of target names and output archive filenames1639StringMap<StringRef> TargetOutputFileNameMap;16401641auto Output = BundlerConfig.OutputFileNames.begin();1642for (auto &Target : BundlerConfig.TargetNames) {1643TargetOutputFileNameMap[Target] = *Output;1644++Output;1645}16461647StringRef IFName = BundlerConfig.InputFileNames.front();16481649if (BundlerConfig.CheckInputArchive) {1650// For a specific processor, a feature either shows up in all target IDs, or1651// does not show up in any target IDs. Otherwise the target ID combination1652// is invalid.1653auto ArchiveError = CheckHeterogeneousArchive(IFName, BundlerConfig);1654if (ArchiveError) {1655return ArchiveError;1656}1657}16581659ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =1660MemoryBuffer::getFileOrSTDIN(IFName, true, false);1661if (std::error_code EC = BufOrErr.getError())1662return createFileError(BundlerConfig.InputFileNames.front(), EC);16631664ArchiveBuffers.push_back(std::move(*BufOrErr));1665Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr =1666Archive::create(ArchiveBuffers.back()->getMemBufferRef());1667if (!LibOrErr)1668return LibOrErr.takeError();16691670auto Archive = std::move(*LibOrErr);16711672Error ArchiveErr = Error::success();1673auto ChildEnd = Archive->child_end();16741675/// Iterate over all bundled code object files in the input archive.1676for (auto ArchiveIter = Archive->child_begin(ArchiveErr);1677ArchiveIter != ChildEnd; ++ArchiveIter) {1678if (ArchiveErr)1679return ArchiveErr;1680auto ArchiveChildNameOrErr = (*ArchiveIter).getName();1681if (!ArchiveChildNameOrErr)1682return ArchiveChildNameOrErr.takeError();16831684StringRef BundledObjectFile = sys::path::filename(*ArchiveChildNameOrErr);16851686auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef();1687if (!CodeObjectBufferRefOrErr)1688return CodeObjectBufferRefOrErr.takeError();16891690auto TempCodeObjectBuffer =1691MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);16921693// Decompress the buffer if necessary.1694Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1695CompressedOffloadBundle::decompress(*TempCodeObjectBuffer,1696BundlerConfig.Verbose);1697if (!DecompressedBufferOrErr)1698return createStringError(1699inconvertibleErrorCode(),1700"Failed to decompress code object: " +1701llvm::toString(DecompressedBufferOrErr.takeError()));17021703MemoryBuffer &CodeObjectBuffer = **DecompressedBufferOrErr;17041705Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1706CreateFileHandler(CodeObjectBuffer, BundlerConfig);1707if (!FileHandlerOrErr)1708return FileHandlerOrErr.takeError();17091710std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;1711assert(FileHandler &&1712"FileHandle creation failed for file in the archive!");17131714if (Error ReadErr = FileHandler->ReadHeader(CodeObjectBuffer))1715return ReadErr;17161717Expected<std::optional<StringRef>> CurBundleIDOrErr =1718FileHandler->ReadBundleStart(CodeObjectBuffer);1719if (!CurBundleIDOrErr)1720return CurBundleIDOrErr.takeError();17211722std::optional<StringRef> OptionalCurBundleID = *CurBundleIDOrErr;1723// No device code in this child, skip.1724if (!OptionalCurBundleID)1725continue;1726StringRef CodeObject = *OptionalCurBundleID;17271728// Process all bundle entries (CodeObjects) found in this child of input1729// archive.1730while (!CodeObject.empty()) {1731SmallVector<StringRef> CompatibleTargets;1732auto CodeObjectInfo = OffloadTargetInfo(CodeObject, BundlerConfig);1733if (getCompatibleOffloadTargets(CodeObjectInfo, CompatibleTargets,1734BundlerConfig)) {1735std::string BundleData;1736raw_string_ostream DataStream(BundleData);1737if (Error Err = FileHandler->ReadBundle(DataStream, CodeObjectBuffer))1738return Err;17391740for (auto &CompatibleTarget : CompatibleTargets) {1741SmallString<128> BundledObjectFileName;1742BundledObjectFileName.assign(BundledObjectFile);1743auto OutputBundleName =1744Twine(llvm::sys::path::stem(BundledObjectFileName) + "-" +1745CodeObject +1746getDeviceLibraryFileName(BundledObjectFileName,1747CodeObjectInfo.TargetID))1748.str();1749// Replace ':' in optional target feature list with '_' to ensure1750// cross-platform validity.1751std::replace(OutputBundleName.begin(), OutputBundleName.end(), ':',1752'_');17531754std::unique_ptr<MemoryBuffer> MemBuf = MemoryBuffer::getMemBufferCopy(1755DataStream.str(), OutputBundleName);1756ArchiveBuffers.push_back(std::move(MemBuf));1757llvm::MemoryBufferRef MemBufRef =1758MemoryBufferRef(*(ArchiveBuffers.back()));17591760// For inserting <CompatibleTarget, list<CodeObject>> entry in1761// OutputArchivesMap.1762if (!OutputArchivesMap.contains(CompatibleTarget)) {17631764std::vector<NewArchiveMember> ArchiveMembers;1765ArchiveMembers.push_back(NewArchiveMember(MemBufRef));1766OutputArchivesMap.insert_or_assign(CompatibleTarget,1767std::move(ArchiveMembers));1768} else {1769OutputArchivesMap[CompatibleTarget].push_back(1770NewArchiveMember(MemBufRef));1771}1772}1773}17741775if (Error Err = FileHandler->ReadBundleEnd(CodeObjectBuffer))1776return Err;17771778Expected<std::optional<StringRef>> NextTripleOrErr =1779FileHandler->ReadBundleStart(CodeObjectBuffer);1780if (!NextTripleOrErr)1781return NextTripleOrErr.takeError();17821783CodeObject = ((*NextTripleOrErr).has_value()) ? **NextTripleOrErr : "";1784} // End of processing of all bundle entries of this child of input archive.1785} // End of while over children of input archive.17861787assert(!ArchiveErr && "Error occurred while reading archive!");17881789/// Write out an archive for each target1790for (auto &Target : BundlerConfig.TargetNames) {1791StringRef FileName = TargetOutputFileNameMap[Target];1792StringMapIterator<std::vector<llvm::NewArchiveMember>> CurArchiveMembers =1793OutputArchivesMap.find(Target);1794if (CurArchiveMembers != OutputArchivesMap.end()) {1795if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(),1796SymtabWritingMode::NormalSymtab,1797getDefaultArchiveKindForHost(), true,1798false, nullptr))1799return WriteErr;1800} else if (!BundlerConfig.AllowMissingBundles) {1801std::string ErrMsg =1802Twine("no compatible code object found for the target '" + Target +1803"' in heterogeneous archive library: " + IFName)1804.str();1805return createStringError(inconvertibleErrorCode(), ErrMsg);1806} else { // Create an empty archive file if no compatible code object is1807// found and "allow-missing-bundles" is enabled. It ensures that1808// the linker using output of this step doesn't complain about1809// the missing input file.1810std::vector<llvm::NewArchiveMember> EmptyArchive;1811EmptyArchive.clear();1812if (Error WriteErr = writeArchive(1813FileName, EmptyArchive, SymtabWritingMode::NormalSymtab,1814getDefaultArchiveKindForHost(), true, false, nullptr))1815return WriteErr;1816}1817}18181819return Error::success();1820}182118221823