llvm-project/clang/lib/Driver/OffloadBundler.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1897 lines
69 KiB
C++
Raw Normal View History

//===- 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/Driver/OffloadBundler.h"
#include "clang/Basic/Cuda.h"
#include "clang/Basic/TargetID.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/Magic.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/Compression.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MD5.h"
#include "llvm/Support/ManagedStatic.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/Timer.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <forward_list>
#include <llvm/Support/Process.h>
#include <memory>
#include <set>
#include <string>
#include <system_error>
#include <utility>
using namespace llvm;
using namespace llvm::object;
using namespace clang;
namespace {
struct CreateClangOffloadBundlerTimerGroup {
static void *call() {
return new TimerGroup("Clang Offload Bundler Timer Group",
"Timer group for clang offload bundler");
}
};
} // namespace
static llvm::ManagedStatic<llvm::TimerGroup,
CreateClangOffloadBundlerTimerGroup>
ClangOffloadBundlerTimerGroup;
/// 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::StringToOffloadArch(TripleOrGPU.second) !=
clang::OffloadArch::UNKNOWN) {
auto KindTriple = TripleOrGPU.first.split('-');
this->OffloadKind = KindTriple.first;
// Enforce optional env field to standardize bundles
llvm::Triple t = llvm::Triple(KindTriple.second);
this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(),
t.getOSName(), t.getEnvironmentName());
this->TargetID = Target.substr(Target.find(TripleOrGPU.second));
} else {
auto KindTriple = TargetFeatures.first.split('-');
this->OffloadKind = KindTriple.first;
// Enforce optional env field to standardize bundles
llvm::Triple t = llvm::Triple(KindTriple.second);
this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(),
t.getOSName(), t.getEnvironmentName());
this->TargetID = "";
}
}
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) ||
(OffloadKind == "hip" && TargetOffloadKind == "hipv4") ||
(OffloadKind == "hipv4" && TargetOffloadKind == "hip"))
return true;
if (BundlerConfig.HipOpenmpCompatible) {
bool HIPCompatibleWithOpenMP = OffloadKind.starts_with_insensitive("hip") &&
TargetOffloadKind == "openmp";
bool OpenMPCompatibleWithHIP =
OffloadKind == "openmp" &&
TargetOffloadKind.starts_with_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) && TargetID == Target.TargetID;
}
std::string OffloadTargetInfo::str() const {
return Twine(OffloadKind + "-" + Triple.str() + "-" + TargetID).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;
}
namespace {
/// 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 `std::nullopt` if there
/// are no more bundles to be read.
virtual Expected<std::optional<StringRef>>
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_ostream &OS,
ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) = 0;
/// Write the marker that initiates a bundle for the triple \a TargetTriple to
/// \a OS.
virtual Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) = 0;
/// Write the marker that closes a bundle for the triple \a TargetTriple to \a
/// OS.
virtual Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) = 0;
/// Write the bundle from \a Input into \a OS.
virtual Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) = 0;
/// Finalize output file.
virtual Error finalizeOutputFile() { return Error::success(); }
/// 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();
});
}
/// Get bundle IDs in \a Input in \a BundleIds.
virtual Error getBundleIDs(MemoryBuffer &Input,
std::set<StringRef> &BundleIds) {
if (Error Err = ReadHeader(Input))
return Err;
return forEachBundle(Input, [&](const BundleInfo &Info) -> Error {
BundleIds.insert(Info.BundleID);
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<Error(const BundleInfo &)> Func) {
while (true) {
Expected<std::optional<StringRef>> 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) {
return llvm::support::endian::read64le(Buffer.data() + pos);
}
/// Write 8-byte integers to a buffer in little-endian format.
static void Write8byteIntegerToBuffer(raw_ostream &OS, uint64_t Val) {
llvm::support::endian::write(OS, Val, llvm::endianness::little);
}
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<BinaryBundleInfo> BundlesInfo;
/// Iterator for the bundle information that is being read.
StringMap<BinaryBundleInfo>::iterator CurBundleInfo;
StringMap<BinaryBundleInfo>::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.
if (llvm::identify_magic(FC) != llvm::file_magic::offload_bundle)
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();
2023-03-15 18:06:34 -07:00
assert(!BundlesInfo.contains(Triple) && "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<std::optional<StringRef>>
ReadBundleStart(MemoryBuffer &Input) final {
if (NextBundleInfo == BundlesInfo.end())
return std::nullopt;
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_ostream &OS,
ArrayRef<std::unique_ptr<MemoryBuffer>> 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_ostream &OS, StringRef TargetTriple) final {
CurWriteBundleTarget = TargetTriple.str();
return Error::success();
}
Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {
return Error::success();
}
Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final {
auto BI = BundlesInfo[CurWriteBundleTarget];
// Pad with 0 to reach specified offset.
size_t CurrentPos = OS.tell();
size_t PaddingSize = BI.Offset > CurrentPos ? BI.Offset - CurrentPos : 0;
for (size_t I = 0; I < PaddingSize; ++I)
OS.write('\0');
assert(OS.tell() == BI.Offset);
OS.write(Input.getBufferStart(), Input.getBufferSize());
return Error::success();
}
};
// 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<StringRef> Create(std::optional<ArrayRef<char>> 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<SmallString<128u>> Files;
};
/// 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<ObjectFile> Obj;
/// Return the input file contents.
StringRef getInputFileContents() const { return Obj->getData(); }
/// Return bundle name (<kind>-<triple>) if the provided section is an offload
/// section.
static Expected<std::optional<StringRef>>
IsOffloadSection(SectionRef CurSection) {
Expected<StringRef> NameOrErr = CurSection.getName();
if (!NameOrErr)
return NameOrErr.takeError();
// If it does not start with the reserved suffix, just skip this section.
if (llvm::identify_magic(*NameOrErr) != llvm::file_magic::offload_bundle)
return std::nullopt;
// 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<ObjectFile> 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<std::optional<StringRef>>
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<std::optional<StringRef>> TripleOrErr =
IsOffloadSection(*CurrentSection);
if (!TripleOrErr)
return TripleOrErr.takeError();
if (*TripleOrErr)
return **TripleOrErr;
}
return std::nullopt;
}
Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); }
Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final {
Expected<StringRef> ContentOrErr = CurrentSection->getContents();
if (!ContentOrErr)
return ContentOrErr.takeError();
StringRef Content = *ContentOrErr;
// Copy fat object contents to the output when extracting host bundle.
[HIP] Allow partial linking for `-fgpu-rdc` (#81700) `-fgpu-rdc` mode allows device functions call device functions in different TU. However, currently all device objects have to be linked together since only one fat binary is supported. This is time consuming for AMDGPU backend since it only supports LTO. There are use cases that objects can be divided into groups in which device functions are self-contained but host functions are not. It is desirable to link/optimize/codegen the device code and generate a fatbin for each group, whereas partially link the host code with `ld -r` or generate a static library by using the `--emit-static-lib` option of clang. This avoids linking all device code together, therefore decreases the linking time for `-fgpu-rdc`. Previously, clang emits an external symbol `__hip_fatbin` for all objects for `-fgpu-rdc`. With this patch, clang emits an unique external symbol `__hip_fatbin_{cuid}` for the fat binary for each object. When a group of objects are linked together to generate a fatbin, the symbols are merged by alias and point to the same fat binary. Each group has its own fat binary. One executable or shared library can have multiple fat binaries. Device linking is done for undefined fab binary symbols only to avoid repeated linking. `__hip_gpubin_handle` is also uniquefied and merged to avoid repeated registering. Symbol `__hip_cuid_{cuid}` is introduced to facilitate debugging and tooling. Fixes: https://github.com/llvm/llvm-project/issues/77018
2024-02-22 13:51:31 -05:00
std::string ModifiedContent;
if (Content.size() == 1u && Content.front() == 0) {
auto HostBundleOrErr = getHostBundle(
StringRef(Input.getBufferStart(), Input.getBufferSize()));
[HIP] Allow partial linking for `-fgpu-rdc` (#81700) `-fgpu-rdc` mode allows device functions call device functions in different TU. However, currently all device objects have to be linked together since only one fat binary is supported. This is time consuming for AMDGPU backend since it only supports LTO. There are use cases that objects can be divided into groups in which device functions are self-contained but host functions are not. It is desirable to link/optimize/codegen the device code and generate a fatbin for each group, whereas partially link the host code with `ld -r` or generate a static library by using the `--emit-static-lib` option of clang. This avoids linking all device code together, therefore decreases the linking time for `-fgpu-rdc`. Previously, clang emits an external symbol `__hip_fatbin` for all objects for `-fgpu-rdc`. With this patch, clang emits an unique external symbol `__hip_fatbin_{cuid}` for the fat binary for each object. When a group of objects are linked together to generate a fatbin, the symbols are merged by alias and point to the same fat binary. Each group has its own fat binary. One executable or shared library can have multiple fat binaries. Device linking is done for undefined fab binary symbols only to avoid repeated linking. `__hip_gpubin_handle` is also uniquefied and merged to avoid repeated registering. Symbol `__hip_cuid_{cuid}` is introduced to facilitate debugging and tooling. Fixes: https://github.com/llvm/llvm-project/issues/77018
2024-02-22 13:51:31 -05:00
if (!HostBundleOrErr)
return HostBundleOrErr.takeError();
ModifiedContent = std::move(*HostBundleOrErr);
Content = ModifiedContent;
}
OS.write(Content.data(), Content.size());
return Error::success();
}
Error WriteHeader(raw_ostream &OS,
ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {
assert(BundlerConfig.HostInputIndex != ~0u &&
"Host input index not defined.");
// Record number of inputs.
NumberOfInputs = Inputs.size();
return Error::success();
}
Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {
++NumberOfProcessedInputs;
return Error::success();
}
Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {
return Error::success();
}
Error finalizeOutputFile() 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");
// 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<StringRef, 8u> 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<StringRef> TempFileOrErr = TempFiles.Create(ArrayRef<char>(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_ostream &OS, MemoryBuffer &Input) final {
return Error::success();
}
private:
Error executeObjcopy(StringRef Objcopy, ArrayRef<StringRef> 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();
}
[HIP] Allow partial linking for `-fgpu-rdc` (#81700) `-fgpu-rdc` mode allows device functions call device functions in different TU. However, currently all device objects have to be linked together since only one fat binary is supported. This is time consuming for AMDGPU backend since it only supports LTO. There are use cases that objects can be divided into groups in which device functions are self-contained but host functions are not. It is desirable to link/optimize/codegen the device code and generate a fatbin for each group, whereas partially link the host code with `ld -r` or generate a static library by using the `--emit-static-lib` option of clang. This avoids linking all device code together, therefore decreases the linking time for `-fgpu-rdc`. Previously, clang emits an external symbol `__hip_fatbin` for all objects for `-fgpu-rdc`. With this patch, clang emits an unique external symbol `__hip_fatbin_{cuid}` for the fat binary for each object. When a group of objects are linked together to generate a fatbin, the symbols are merged by alias and point to the same fat binary. Each group has its own fat binary. One executable or shared library can have multiple fat binaries. Device linking is done for undefined fab binary symbols only to avoid repeated linking. `__hip_gpubin_handle` is also uniquefied and merged to avoid repeated registering. Symbol `__hip_cuid_{cuid}` is introduced to facilitate debugging and tooling. Fixes: https://github.com/llvm/llvm-project/issues/77018
2024-02-22 13:51:31 -05:00
Expected<std::string> getHostBundle(StringRef Input) {
[HIP] Allow partial linking for `-fgpu-rdc` (#81700) `-fgpu-rdc` mode allows device functions call device functions in different TU. However, currently all device objects have to be linked together since only one fat binary is supported. This is time consuming for AMDGPU backend since it only supports LTO. There are use cases that objects can be divided into groups in which device functions are self-contained but host functions are not. It is desirable to link/optimize/codegen the device code and generate a fatbin for each group, whereas partially link the host code with `ld -r` or generate a static library by using the `--emit-static-lib` option of clang. This avoids linking all device code together, therefore decreases the linking time for `-fgpu-rdc`. Previously, clang emits an external symbol `__hip_fatbin` for all objects for `-fgpu-rdc`. With this patch, clang emits an unique external symbol `__hip_fatbin_{cuid}` for the fat binary for each object. When a group of objects are linked together to generate a fatbin, the symbols are merged by alias and point to the same fat binary. Each group has its own fat binary. One executable or shared library can have multiple fat binaries. Device linking is done for undefined fab binary symbols only to avoid repeated linking. `__hip_gpubin_handle` is also uniquefied and merged to avoid repeated registering. Symbol `__hip_cuid_{cuid}` is introduced to facilitate debugging and tooling. Fixes: https://github.com/llvm/llvm-project/issues/77018
2024-02-22 13:51:31 -05:00
TempFileHandlerRAII TempFiles;
auto ModifiedObjPathOrErr = TempFiles.Create(std::nullopt);
if (!ModifiedObjPathOrErr)
return ModifiedObjPathOrErr.takeError();
StringRef ModifiedObjPath = *ModifiedObjPathOrErr;
BumpPtrAllocator Alloc;
StringSaver SS{Alloc};
SmallVector<StringRef, 16> ObjcopyArgs{"llvm-objcopy"};
ObjcopyArgs.push_back("--regex");
ObjcopyArgs.push_back("--remove-section=__CLANG_OFFLOAD_BUNDLE__.*");
ObjcopyArgs.push_back("--");
StringRef ObjcopyInputFileName;
// When unbundling an archive, the content of each object file in the
// archive is passed to this function by parameter Input, which is different
// from the content of the original input archive file, therefore it needs
// to be saved to a temporary file before passed to llvm-objcopy. Otherwise,
// Input is the same as the content of the original input file, therefore
// temporary file is not needed.
if (StringRef(BundlerConfig.FilesType).starts_with("a")) {
auto InputFileOrErr =
TempFiles.Create(ArrayRef<char>(Input.data(), Input.size()));
if (!InputFileOrErr)
return InputFileOrErr.takeError();
ObjcopyInputFileName = *InputFileOrErr;
} else
ObjcopyInputFileName = BundlerConfig.InputFileNames.front();
ObjcopyArgs.push_back(ObjcopyInputFileName);
[HIP] Allow partial linking for `-fgpu-rdc` (#81700) `-fgpu-rdc` mode allows device functions call device functions in different TU. However, currently all device objects have to be linked together since only one fat binary is supported. This is time consuming for AMDGPU backend since it only supports LTO. There are use cases that objects can be divided into groups in which device functions are self-contained but host functions are not. It is desirable to link/optimize/codegen the device code and generate a fatbin for each group, whereas partially link the host code with `ld -r` or generate a static library by using the `--emit-static-lib` option of clang. This avoids linking all device code together, therefore decreases the linking time for `-fgpu-rdc`. Previously, clang emits an external symbol `__hip_fatbin` for all objects for `-fgpu-rdc`. With this patch, clang emits an unique external symbol `__hip_fatbin_{cuid}` for the fat binary for each object. When a group of objects are linked together to generate a fatbin, the symbols are merged by alias and point to the same fat binary. Each group has its own fat binary. One executable or shared library can have multiple fat binaries. Device linking is done for undefined fab binary symbols only to avoid repeated linking. `__hip_gpubin_handle` is also uniquefied and merged to avoid repeated registering. Symbol `__hip_cuid_{cuid}` is introduced to facilitate debugging and tooling. Fixes: https://github.com/llvm/llvm-project/issues/77018
2024-02-22 13:51:31 -05:00
ObjcopyArgs.push_back(ModifiedObjPath);
if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs))
return std::move(Err);
auto BufOrErr = MemoryBuffer::getFile(ModifiedObjPath);
if (!BufOrErr)
return createStringError(BufOrErr.getError(),
"Failed to read back the modified object file");
return BufOrErr->get()->getBuffer().str();
}
};
/// 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<std::optional<StringRef>>
ReadBundleStart(MemoryBuffer &Input) final {
StringRef FC = Input.getBuffer();
// Find start of the bundle.
ReadChars = FC.find(BundleStartString, ReadChars);
if (ReadChars == FC.npos)
return std::nullopt;
// 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 std::nullopt;
// 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_ostream &OS,
ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {
return Error::success();
}
Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {
OS << BundleStartString << TargetTriple << "\n";
return Error::success();
}
Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {
OS << BundleEndString << TargetTriple << "\n";
return Error::success();
}
Error WriteBundle(raw_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();
}
};
} // namespace
/// 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<FileHandler>
CreateObjectFileHandler(MemoryBuffer &FirstInput,
const OffloadBundlerConfig &BundlerConfig) {
// Check if the input file format is one that we know how to deal with.
Expected<std::unique_ptr<Binary>> 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<ObjectFile>(*BinaryOrErr))
return std::make_unique<BinaryFileHandler>(BundlerConfig);
// Otherwise create an object file handler. The handler will be owned by the
// client of this function.
return std::make_unique<ObjectFileHandler>(
std::unique_ptr<ObjectFile>(cast<ObjectFile>(BinaryOrErr->release())),
BundlerConfig);
}
/// Return an appropriate handler given the input files and options.
static Expected<std::unique_ptr<FileHandler>>
CreateFileHandler(MemoryBuffer &FirstInput,
const OffloadBundlerConfig &BundlerConfig) {
std::string FilesType = BundlerConfig.FilesType;
if (FilesType == "i")
return std::make_unique<TextFileHandler>(/*Comment=*/"//");
if (FilesType == "ii")
return std::make_unique<TextFileHandler>(/*Comment=*/"//");
if (FilesType == "cui")
return std::make_unique<TextFileHandler>(/*Comment=*/"//");
if (FilesType == "hipi")
return std::make_unique<TextFileHandler>(/*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<TextFileHandler>(/*Comment=*/"#");
if (FilesType == "ll")
return std::make_unique<TextFileHandler>(/*Comment=*/";");
if (FilesType == "bc")
return std::make_unique<BinaryFileHandler>(BundlerConfig);
if (FilesType == "s")
return std::make_unique<TextFileHandler>(/*Comment=*/"#");
if (FilesType == "o")
return CreateObjectFileHandler(FirstInput, BundlerConfig);
if (FilesType == "a")
return CreateObjectFileHandler(FirstInput, BundlerConfig);
if (FilesType == "gch")
return std::make_unique<BinaryFileHandler>(BundlerConfig);
if (FilesType == "ast")
return std::make_unique<BinaryFileHandler>(BundlerConfig);
return createStringError(errc::invalid_argument,
"'" + FilesType + "': invalid file type specified");
}
OffloadBundlerConfig::OffloadBundlerConfig()
: CompressedBundleVersion(CompressedOffloadBundle::DefaultVersion) {
if (llvm::compression::zstd::isAvailable()) {
CompressionFormat = llvm::compression::Format::Zstd;
// Compression level 3 is usually sufficient for zstd since long distance
// matching is enabled.
CompressionLevel = 3;
} else if (llvm::compression::zlib::isAvailable()) {
CompressionFormat = llvm::compression::Format::Zlib;
// Use default level for zlib since higher level does not have significant
// improvement.
CompressionLevel = llvm::compression::zlib::DefaultCompression;
}
auto IgnoreEnvVarOpt =
llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_IGNORE_ENV_VAR");
if (IgnoreEnvVarOpt.has_value() && IgnoreEnvVarOpt.value() == "1")
return;
auto VerboseEnvVarOpt = llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_VERBOSE");
if (VerboseEnvVarOpt.has_value())
Verbose = VerboseEnvVarOpt.value() == "1";
auto CompressEnvVarOpt =
llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESS");
if (CompressEnvVarOpt.has_value())
Compress = CompressEnvVarOpt.value() == "1";
auto CompressionLevelEnvVarOpt =
llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESSION_LEVEL");
if (CompressionLevelEnvVarOpt.has_value()) {
llvm::StringRef CompressionLevelStr = CompressionLevelEnvVarOpt.value();
int Level;
if (!CompressionLevelStr.getAsInteger(10, Level))
CompressionLevel = Level;
else
llvm::errs()
<< "Warning: Invalid value for OFFLOAD_BUNDLER_COMPRESSION_LEVEL: "
<< CompressionLevelStr.str() << ". Ignoring it.\n";
}
auto CompressedBundleFormatVersionOpt =
llvm::sys::Process::GetEnv("COMPRESSED_BUNDLE_FORMAT_VERSION");
if (CompressedBundleFormatVersionOpt.has_value()) {
llvm::StringRef VersionStr = CompressedBundleFormatVersionOpt.value();
uint16_t Version;
if (!VersionStr.getAsInteger(10, Version)) {
if (Version >= 2 && Version <= 3)
CompressedBundleVersion = Version;
else
llvm::errs()
<< "Warning: Invalid value for COMPRESSED_BUNDLE_FORMAT_VERSION: "
<< VersionStr.str()
<< ". Valid values are 2 or 3. Using default version "
<< CompressedBundleVersion << ".\n";
} else
llvm::errs()
<< "Warning: Invalid value for COMPRESSED_BUNDLE_FORMAT_VERSION: "
<< VersionStr.str() << ". Using default version "
<< CompressedBundleVersion << ".\n";
}
}
// Utility function to format numbers with commas
static std::string formatWithCommas(unsigned long long Value) {
std::string Num = std::to_string(Value);
int InsertPosition = Num.length() - 3;
while (InsertPosition > 0) {
Num.insert(InsertPosition, ",");
InsertPosition -= 3;
}
return Num;
}
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
CompressedOffloadBundle::compress(llvm::compression::Params P,
const llvm::MemoryBuffer &Input,
uint16_t Version, bool Verbose) {
if (!llvm::compression::zstd::isAvailable() &&
!llvm::compression::zlib::isAvailable())
return createStringError(llvm::inconvertibleErrorCode(),
"Compression not supported");
llvm::Timer HashTimer("Hash Calculation Timer", "Hash calculation time",
*ClangOffloadBundlerTimerGroup);
if (Verbose)
HashTimer.startTimer();
llvm::MD5 Hash;
llvm::MD5::MD5Result Result;
Hash.update(Input.getBuffer());
Hash.final(Result);
uint64_t TruncatedHash = Result.low();
if (Verbose)
HashTimer.stopTimer();
SmallVector<uint8_t, 0> CompressedBuffer;
auto BufferUint8 = llvm::ArrayRef<uint8_t>(
reinterpret_cast<const uint8_t *>(Input.getBuffer().data()),
Input.getBuffer().size());
llvm::Timer CompressTimer("Compression Timer", "Compression time",
*ClangOffloadBundlerTimerGroup);
if (Verbose)
CompressTimer.startTimer();
llvm::compression::compress(P, BufferUint8, CompressedBuffer);
if (Verbose)
CompressTimer.stopTimer();
uint16_t CompressionMethod = static_cast<uint16_t>(P.format);
// Store sizes in 64-bit variables first
uint64_t UncompressedSize64 = Input.getBuffer().size();
uint64_t TotalFileSize64;
// Calculate total file size based on version
if (Version == 2) {
// For V2, ensure the sizes don't exceed 32-bit limit
if (UncompressedSize64 > std::numeric_limits<uint32_t>::max())
return createStringError(llvm::inconvertibleErrorCode(),
"Uncompressed size exceeds version 2 limit");
if ((MagicNumber.size() + sizeof(uint32_t) + sizeof(Version) +
sizeof(CompressionMethod) + sizeof(uint32_t) + sizeof(TruncatedHash) +
CompressedBuffer.size()) > std::numeric_limits<uint32_t>::max())
return createStringError(llvm::inconvertibleErrorCode(),
"Total file size exceeds version 2 limit");
TotalFileSize64 = MagicNumber.size() + sizeof(uint32_t) + sizeof(Version) +
sizeof(CompressionMethod) + sizeof(uint32_t) +
sizeof(TruncatedHash) + CompressedBuffer.size();
} else { // Version 3
TotalFileSize64 = MagicNumber.size() + sizeof(uint64_t) + sizeof(Version) +
sizeof(CompressionMethod) + sizeof(uint64_t) +
sizeof(TruncatedHash) + CompressedBuffer.size();
}
SmallVector<char, 0> FinalBuffer;
llvm::raw_svector_ostream OS(FinalBuffer);
OS << MagicNumber;
OS.write(reinterpret_cast<const char *>(&Version), sizeof(Version));
OS.write(reinterpret_cast<const char *>(&CompressionMethod),
sizeof(CompressionMethod));
// Write size fields according to version
if (Version == 2) {
uint32_t TotalFileSize32 = static_cast<uint32_t>(TotalFileSize64);
uint32_t UncompressedSize32 = static_cast<uint32_t>(UncompressedSize64);
OS.write(reinterpret_cast<const char *>(&TotalFileSize32),
sizeof(TotalFileSize32));
OS.write(reinterpret_cast<const char *>(&UncompressedSize32),
sizeof(UncompressedSize32));
} else { // Version 3
OS.write(reinterpret_cast<const char *>(&TotalFileSize64),
sizeof(TotalFileSize64));
OS.write(reinterpret_cast<const char *>(&UncompressedSize64),
sizeof(UncompressedSize64));
}
OS.write(reinterpret_cast<const char *>(&TruncatedHash),
sizeof(TruncatedHash));
OS.write(reinterpret_cast<const char *>(CompressedBuffer.data()),
CompressedBuffer.size());
if (Verbose) {
auto MethodUsed =
P.format == llvm::compression::Format::Zstd ? "zstd" : "zlib";
double CompressionRate =
static_cast<double>(UncompressedSize64) / CompressedBuffer.size();
double CompressionTimeSeconds = CompressTimer.getTotalTime().getWallTime();
double CompressionSpeedMBs =
(UncompressedSize64 / (1024.0 * 1024.0)) / CompressionTimeSeconds;
llvm::errs() << "Compressed bundle format version: " << Version << "\n"
<< "Total file size (including headers): "
<< formatWithCommas(TotalFileSize64) << " bytes\n"
<< "Compression method used: " << MethodUsed << "\n"
<< "Compression level: " << P.level << "\n"
<< "Binary size before compression: "
<< formatWithCommas(UncompressedSize64) << " bytes\n"
<< "Binary size after compression: "
<< formatWithCommas(CompressedBuffer.size()) << " bytes\n"
<< "Compression rate: "
<< llvm::format("%.2lf", CompressionRate) << "\n"
<< "Compression ratio: "
<< llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"
<< "Compression speed: "
<< llvm::format("%.2lf MB/s", CompressionSpeedMBs) << "\n"
<< "Truncated MD5 hash: "
<< llvm::format_hex(TruncatedHash, 16) << "\n";
}
return llvm::MemoryBuffer::getMemBufferCopy(
llvm::StringRef(FinalBuffer.data(), FinalBuffer.size()));
}
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input,
bool Verbose) {
StringRef Blob = Input.getBuffer();
// Check minimum header size (using V1 as it's the smallest)
if (Blob.size() < V1HeaderSize)
return llvm::MemoryBuffer::getMemBufferCopy(Blob);
if (llvm::identify_magic(Blob) !=
llvm::file_magic::offload_bundle_compressed) {
if (Verbose)
llvm::errs() << "Uncompressed bundle.\n";
return llvm::MemoryBuffer::getMemBufferCopy(Blob);
}
size_t CurrentOffset = MagicSize;
// Read version
uint16_t ThisVersion;
memcpy(&ThisVersion, Blob.data() + CurrentOffset, sizeof(uint16_t));
CurrentOffset += VersionFieldSize;
// Verify header size based on version
if (ThisVersion >= 2 && ThisVersion <= 3) {
size_t RequiredSize = (ThisVersion == 2) ? V2HeaderSize : V3HeaderSize;
if (Blob.size() < RequiredSize)
return createStringError(inconvertibleErrorCode(),
"Compressed bundle header size too small");
}
// Read compression method
uint16_t CompressionMethod;
memcpy(&CompressionMethod, Blob.data() + CurrentOffset, sizeof(uint16_t));
CurrentOffset += MethodFieldSize;
// Read total file size (version 2+)
uint64_t TotalFileSize = 0;
if (ThisVersion >= 2) {
if (ThisVersion == 2) {
uint32_t TotalFileSize32;
memcpy(&TotalFileSize32, Blob.data() + CurrentOffset, sizeof(uint32_t));
TotalFileSize = TotalFileSize32;
CurrentOffset += FileSizeFieldSizeV2;
} else { // Version 3
memcpy(&TotalFileSize, Blob.data() + CurrentOffset, sizeof(uint64_t));
CurrentOffset += FileSizeFieldSizeV3;
}
}
// Read uncompressed size
uint64_t UncompressedSize = 0;
if (ThisVersion <= 2) {
uint32_t UncompressedSize32;
memcpy(&UncompressedSize32, Blob.data() + CurrentOffset, sizeof(uint32_t));
UncompressedSize = UncompressedSize32;
CurrentOffset += UncompressedSizeFieldSizeV2;
} else { // Version 3
memcpy(&UncompressedSize, Blob.data() + CurrentOffset, sizeof(uint64_t));
CurrentOffset += UncompressedSizeFieldSizeV3;
}
// Read hash
uint64_t StoredHash;
memcpy(&StoredHash, Blob.data() + CurrentOffset, sizeof(uint64_t));
CurrentOffset += HashFieldSize;
// Determine compression format
llvm::compression::Format CompressionFormat;
if (CompressionMethod ==
static_cast<uint16_t>(llvm::compression::Format::Zlib))
CompressionFormat = llvm::compression::Format::Zlib;
else if (CompressionMethod ==
static_cast<uint16_t>(llvm::compression::Format::Zstd))
CompressionFormat = llvm::compression::Format::Zstd;
else
return createStringError(inconvertibleErrorCode(),
"Unknown compressing method");
llvm::Timer DecompressTimer("Decompression Timer", "Decompression time",
*ClangOffloadBundlerTimerGroup);
if (Verbose)
DecompressTimer.startTimer();
SmallVector<uint8_t, 0> DecompressedData;
StringRef CompressedData = Blob.substr(CurrentOffset);
if (llvm::Error DecompressionError = llvm::compression::decompress(
CompressionFormat, llvm::arrayRefFromStringRef(CompressedData),
DecompressedData, UncompressedSize))
return createStringError(inconvertibleErrorCode(),
"Could not decompress embedded file contents: " +
llvm::toString(std::move(DecompressionError)));
if (Verbose) {
DecompressTimer.stopTimer();
double DecompressionTimeSeconds =
DecompressTimer.getTotalTime().getWallTime();
// Recalculate MD5 hash for integrity check
llvm::Timer HashRecalcTimer("Hash Recalculation Timer",
"Hash recalculation time",
*ClangOffloadBundlerTimerGroup);
HashRecalcTimer.startTimer();
llvm::MD5 Hash;
llvm::MD5::MD5Result Result;
Hash.update(llvm::ArrayRef<uint8_t>(DecompressedData.data(),
DecompressedData.size()));
Hash.final(Result);
uint64_t RecalculatedHash = Result.low();
HashRecalcTimer.stopTimer();
bool HashMatch = (StoredHash == RecalculatedHash);
double CompressionRate =
static_cast<double>(UncompressedSize) / CompressedData.size();
double DecompressionSpeedMBs =
(UncompressedSize / (1024.0 * 1024.0)) / DecompressionTimeSeconds;
llvm::errs() << "Compressed bundle format version: " << ThisVersion << "\n";
if (ThisVersion >= 2)
llvm::errs() << "Total file size (from header): "
<< formatWithCommas(TotalFileSize) << " bytes\n";
llvm::errs() << "Decompression method: "
<< (CompressionFormat == llvm::compression::Format::Zlib
? "zlib"
: "zstd")
<< "\n"
<< "Size before decompression: "
<< formatWithCommas(CompressedData.size()) << " bytes\n"
<< "Size after decompression: "
<< formatWithCommas(UncompressedSize) << " bytes\n"
<< "Compression rate: "
<< llvm::format("%.2lf", CompressionRate) << "\n"
<< "Compression ratio: "
<< llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"
<< "Decompression speed: "
<< llvm::format("%.2lf MB/s", DecompressionSpeedMBs) << "\n"
<< "Stored hash: " << llvm::format_hex(StoredHash, 16) << "\n"
<< "Recalculated hash: "
<< llvm::format_hex(RecalculatedHash, 16) << "\n"
<< "Hashes match: " << (HashMatch ? "Yes" : "No") << "\n";
}
return llvm::MemoryBuffer::getMemBufferCopy(
llvm::toStringRef(DecompressedData));
}
// List bundle IDs. Return true if an error was found.
Error OffloadBundler::ListBundleIDsInFile(
StringRef InputFileName, const OffloadBundlerConfig &BundlerConfig) {
// Open Input file.
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getFileOrSTDIN(InputFileName, /*IsText=*/true);
if (std::error_code EC = CodeOrErr.getError())
return createFileError(InputFileName, EC);
// Decompress the input if necessary.
Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);
if (!DecompressedBufferOrErr)
return createStringError(
inconvertibleErrorCode(),
"Failed to decompress input: " +
llvm::toString(DecompressedBufferOrErr.takeError()));
MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr;
// Select the right files handler.
Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
CreateFileHandler(DecompressedInput, BundlerConfig);
if (!FileHandlerOrErr)
return FileHandlerOrErr.takeError();
std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
assert(FH);
return FH->listBundleIDs(DecompressedInput);
}
/// @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(const OffloadTargetInfo &CodeObjectInfo,
const 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 Processors mismatch.
llvm::StringMap<bool> CodeObjectFeatureMap, TargetFeatureMap;
std::optional<StringRef> CodeObjectProc = clang::parseTargetID(
CodeObjectInfo.Triple, CodeObjectInfo.TargetID, &CodeObjectFeatureMap);
std::optional<StringRef> TargetProc = clang::parseTargetID(
TargetInfo.Triple, TargetInfo.TargetID, &TargetFeatureMap);
// Both TargetProc and CodeObjectProc can't be empty here.
if (!TargetProc || !CodeObjectProc ||
CodeObjectProc.value() != TargetProc.value()) {
DEBUG_WITH_TYPE("CodeObjectCompatibility",
dbgs() << "Incompatible: Processor mismatch \t[CodeObject: "
<< CodeObjectInfo.str()
<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");
return false;
}
// Incompatible if CodeObject has more features than Target, irrespective of
// type or sign of features.
if (CodeObjectFeatureMap.getNumItems() > TargetFeatureMap.getNumItems()) {
DEBUG_WITH_TYPE("CodeObjectCompatibility",
dbgs() << "Incompatible: CodeObject has more features "
"than target \t[CodeObject: "
<< CodeObjectInfo.str()
<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");
return false;
}
// Compatible if each target feature specified by target is compatible with
// target feature of code object. The target feature is compatible if the
// code object does not specify it (meaning Any), or if it specifies it
// with the same value (meaning On or Off).
for (const auto &CodeObjectFeature : CodeObjectFeatureMap) {
auto TargetFeature = TargetFeatureMap.find(CodeObjectFeature.getKey());
if (TargetFeature == TargetFeatureMap.end()) {
DEBUG_WITH_TYPE(
"CodeObjectCompatibility",
dbgs()
<< "Incompatible: Value of CodeObject's non-ANY feature is "
"not matching with Target feature's ANY value \t[CodeObject: "
<< CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()
<< "]\n");
return false;
} else if (TargetFeature->getValue() != CodeObjectFeature.getValue()) {
DEBUG_WITH_TYPE(
"CodeObjectCompatibility",
dbgs() << "Incompatible: Value of CodeObject's non-ANY feature is "
"not matching with Target feature's non-ANY value "
"\t[CodeObject: "
<< CodeObjectInfo.str()
<< "]\t:\t[Target: " << TargetInfo.str() << "]\n");
return false;
}
}
// CodeObject is compatible if all features of Target are:
// - either, present in the Code Object's features map with the same sign,
// - or, the feature is missing from CodeObjects's features map i.e. it is
// set to ANY
DEBUG_WITH_TYPE(
"CodeObjectCompatibility",
dbgs() << "Compatible: Target IDs are compatible \t[CodeObject: "
<< CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()
<< "]\n");
return true;
}
/// Bundle the files. Return true if an error was found.
Error OffloadBundler::BundleFiles() {
std::error_code EC;
// Create a buffer to hold the content before compressing.
SmallVector<char, 0> Buffer;
llvm::raw_svector_ostream BufferStream(Buffer);
// Open input files.
SmallVector<std::unique_ptr<MemoryBuffer>, 8u> InputBuffers;
InputBuffers.reserve(BundlerConfig.InputFileNames.size());
for (auto &I : BundlerConfig.InputFileNames) {
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getFileOrSTDIN(I, /*IsText=*/true);
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<std::unique_ptr<FileHandler>> FileHandlerOrErr = CreateFileHandler(
*InputBuffers[BundlerConfig.AllowNoHost ? 0
: BundlerConfig.HostInputIndex],
BundlerConfig);
if (!FileHandlerOrErr)
return FileHandlerOrErr.takeError();
std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
assert(FH);
// Write header.
if (Error Err = FH->WriteHeader(BufferStream, 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(BufferStream, Triple))
return Err;
if (Error Err = FH->WriteBundle(BufferStream, **Input))
return Err;
if (Error Err = FH->WriteBundleEnd(BufferStream, Triple))
return Err;
++Input;
}
raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), EC,
sys::fs::OF_None);
if (EC)
return createFileError(BundlerConfig.OutputFileNames.front(), EC);
SmallVector<char, 0> CompressedBuffer;
if (BundlerConfig.Compress) {
std::unique_ptr<llvm::MemoryBuffer> BufferMemory =
llvm::MemoryBuffer::getMemBufferCopy(
llvm::StringRef(Buffer.data(), Buffer.size()));
auto CompressionResult = CompressedOffloadBundle::compress(
{BundlerConfig.CompressionFormat, BundlerConfig.CompressionLevel,
/*zstdEnableLdm=*/true},
*BufferMemory, BundlerConfig.CompressedBundleVersion,
BundlerConfig.Verbose);
if (auto Error = CompressionResult.takeError())
return Error;
auto CompressedMemBuffer = std::move(CompressionResult.get());
CompressedBuffer.assign(CompressedMemBuffer->getBufferStart(),
CompressedMemBuffer->getBufferEnd());
} else
CompressedBuffer = Buffer;
OutputFile.write(CompressedBuffer.data(), CompressedBuffer.size());
return FH->finalizeOutputFile();
}
// Unbundle the files. Return true if an error was found.
Error OffloadBundler::UnbundleFiles() {
// Open Input file.
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getFileOrSTDIN(BundlerConfig.InputFileNames.front(),
/*IsText=*/true);
if (std::error_code EC = CodeOrErr.getError())
return createFileError(BundlerConfig.InputFileNames.front(), EC);
// Decompress the input if necessary.
Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);
if (!DecompressedBufferOrErr)
return createStringError(
inconvertibleErrorCode(),
"Failed to decompress input: " +
llvm::toString(DecompressedBufferOrErr.takeError()));
MemoryBuffer &Input = **DecompressedBufferOrErr;
// Select the right files handler.
Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
CreateFileHandler(Input, BundlerConfig);
if (!FileHandlerOrErr)
return FileHandlerOrErr.takeError();
std::unique_ptr<FileHandler> &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<StringRef> 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<std::optional<StringRef>> 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.begin();
for (auto E = Worklist.end(); Output != E; Output++) {
if (isCodeObjectCompatible(
OffloadTargetInfo(CurTriple, BundlerConfig),
OffloadTargetInfo((*Output).first(), BundlerConfig))) {
break;
}
}
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<StringRef> 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 ||
BundlerConfig.AllowMissingBundles))
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 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<StringRef> &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();
}
// Check that each code object file in the input archive conforms to following
// rule: for a specific processor, a feature either shows up in all target IDs,
// or does not show up in any target IDs. Otherwise the target ID combination is
// invalid.
static Error
CheckHeterogeneousArchive(StringRef ArchiveName,
const OffloadBundlerConfig &BundlerConfig) {
std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;
ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =
MemoryBuffer::getFileOrSTDIN(ArchiveName, true, false);
if (std::error_code EC = BufOrErr.getError())
return createFileError(ArchiveName, EC);
ArchiveBuffers.push_back(std::move(*BufOrErr));
Expected<std::unique_ptr<llvm::object::Archive>> 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();
auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef();
if (!CodeObjectBufferRefOrErr)
return CodeObjectBufferRefOrErr.takeError();
auto CodeObjectBuffer =
MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);
Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
CreateFileHandler(*CodeObjectBuffer, BundlerConfig);
if (!FileHandlerOrErr)
return FileHandlerOrErr.takeError();
std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;
assert(FileHandler);
std::set<StringRef> BundleIds;
auto CodeObjectFileError =
FileHandler->getBundleIDs(*CodeObjectBuffer, BundleIds);
if (CodeObjectFileError)
return CodeObjectFileError;
auto &&ConflictingArchs = clang::getConflictTargetIDCombination(BundleIds);
if (ConflictingArchs) {
std::string ErrMsg =
Twine("conflicting TargetIDs [" + ConflictingArchs.value().first +
", " + ConflictingArchs.value().second + "] found in " +
ArchiveChildNameOrErr.get() + " of " + ArchiveName)
.str();
return createStringError(inconvertibleErrorCode(), ErrMsg);
}
}
return ArchiveErr;
}
/// 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
/// <<Parent Bundle Name>-<CodeObject's TargetID>>, with ':' replaced with '_'.
Error OffloadBundler::UnbundleArchive() {
std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;
/// Map of target names with list of object files that will form the device
/// specific archive for that target
StringMap<std::vector<NewArchiveMember>> OutputArchivesMap;
// Map of target names and output archive filenames
StringMap<StringRef> TargetOutputFileNameMap;
auto Output = BundlerConfig.OutputFileNames.begin();
for (auto &Target : BundlerConfig.TargetNames) {
TargetOutputFileNameMap[Target] = *Output;
++Output;
}
StringRef IFName = BundlerConfig.InputFileNames.front();
if (BundlerConfig.CheckInputArchive) {
// For a specific processor, a feature either shows up in all target IDs, or
// does not show up in any target IDs. Otherwise the target ID combination
// is invalid.
auto ArchiveError = CheckHeterogeneousArchive(IFName, BundlerConfig);
if (ArchiveError) {
return ArchiveError;
}
}
ErrorOr<std::unique_ptr<MemoryBuffer>> 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<std::unique_ptr<llvm::object::Archive>> 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 TempCodeObjectBuffer =
MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);
// Decompress the buffer if necessary.
Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
CompressedOffloadBundle::decompress(*TempCodeObjectBuffer,
BundlerConfig.Verbose);
if (!DecompressedBufferOrErr)
return createStringError(
inconvertibleErrorCode(),
"Failed to decompress code object: " +
llvm::toString(DecompressedBufferOrErr.takeError()));
MemoryBuffer &CodeObjectBuffer = **DecompressedBufferOrErr;
Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
CreateFileHandler(CodeObjectBuffer, BundlerConfig);
if (!FileHandlerOrErr)
return FileHandlerOrErr.takeError();
std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;
assert(FileHandler &&
"FileHandle creation failed for file in the archive!");
if (Error ReadErr = FileHandler->ReadHeader(CodeObjectBuffer))
return ReadErr;
Expected<std::optional<StringRef>> CurBundleIDOrErr =
FileHandler->ReadBundleStart(CodeObjectBuffer);
if (!CurBundleIDOrErr)
return CurBundleIDOrErr.takeError();
std::optional<StringRef> 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<StringRef> CompatibleTargets;
auto CodeObjectInfo = OffloadTargetInfo(CodeObject, BundlerConfig);
if (getCompatibleOffloadTargets(CodeObjectInfo, CompatibleTargets,
BundlerConfig)) {
std::string BundleData;
raw_string_ostream DataStream(BundleData);
if (Error Err = FileHandler->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.TargetID))
.str();
// Replace ':' in optional target feature list with '_' to ensure
// cross-platform validity.
std::replace(OutputBundleName.begin(), OutputBundleName.end(), ':',
'_');
std::unique_ptr<MemoryBuffer> MemBuf = MemoryBuffer::getMemBufferCopy(
DataStream.str(), OutputBundleName);
ArchiveBuffers.push_back(std::move(MemBuf));
llvm::MemoryBufferRef MemBufRef =
MemoryBufferRef(*(ArchiveBuffers.back()));
// For inserting <CompatibleTarget, list<CodeObject>> entry in
// OutputArchivesMap.
OutputArchivesMap[CompatibleTarget].push_back(
NewArchiveMember(MemBufRef));
}
}
if (Error Err = FileHandler->ReadBundleEnd(CodeObjectBuffer))
return Err;
Expected<std::optional<StringRef>> NextTripleOrErr =
FileHandler->ReadBundleStart(CodeObjectBuffer);
if (!NextTripleOrErr)
return NextTripleOrErr.takeError();
CodeObject = ((*NextTripleOrErr).has_value()) ? **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<std::vector<llvm::NewArchiveMember>> CurArchiveMembers =
OutputArchivesMap.find(Target);
if (CurArchiveMembers != OutputArchivesMap.end()) {
if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(),
SymtabWritingMode::NormalSymtab,
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<llvm::NewArchiveMember> EmptyArchive;
EmptyArchive.clear();
if (Error WriteErr = writeArchive(
FileName, EmptyArchive, SymtabWritingMode::NormalSymtab,
getDefaultArchiveKindForHost(), true, false, nullptr))
return WriteErr;
}
}
return Error::success();
}