mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 03:56:42 +00:00

In COMGR we hash the header of compressed bundles. For this we take the first bytes of the buffer (according to the maximum header size) and hash them. To have a more stable API, and to be able to pick only the hash field (which is the only one we are actually interested in) of the header, we propose a version independent header version that is common to all versions.
1979 lines
72 KiB
C++
1979 lines
72 KiB
C++
//===- 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/Compiler.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) {
|
|
|
|
// <kind>-<triple>[-<target id>[:target features]]
|
|
// <triple> := <arch>-<vendor>-<os>-<env>
|
|
SmallVector<StringRef, 6> Components;
|
|
Target.split(Components, '-', /*MaxSplit=*/5);
|
|
assert((Components.size() == 5 || Components.size() == 6) &&
|
|
"malformed target string");
|
|
|
|
StringRef TargetIdWithFeature =
|
|
Components.size() == 6 ? Components.back() : "";
|
|
StringRef TargetId = TargetIdWithFeature.split(':').first;
|
|
if (!TargetId.empty() &&
|
|
clang::StringToOffloadArch(TargetId) != clang::OffloadArch::UNKNOWN)
|
|
this->TargetID = TargetIdWithFeature;
|
|
else
|
|
this->TargetID = "";
|
|
|
|
this->OffloadKind = Components.front();
|
|
ArrayRef<StringRef> TripleSlice{&Components[1], /*length=*/4};
|
|
llvm::Triple T = llvm::Triple(llvm::join(TripleSlice, "-"));
|
|
this->Triple = llvm::Triple(T.getArchName(), T.getVendorName(), T.getOSName(),
|
|
T.getEnvironmentName());
|
|
}
|
|
|
|
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 {
|
|
std::string NormalizedTriple;
|
|
// Unfortunately we need some special sauce for AMDGPU because all the runtime
|
|
// assumes the triple to be "amdgcn-amd-amdhsa-" (empty environment) instead
|
|
// of "amdgcn-amd-amdhsa-unknown". It's gonna be very tricky to patch
|
|
// different layers of runtime.
|
|
if (Triple.isAMDGPU()) {
|
|
NormalizedTriple = Triple.normalize(Triple::CanonicalForm::THREE_IDENT);
|
|
NormalizedTriple.push_back('-');
|
|
} else {
|
|
NormalizedTriple = Triple.normalize(Triple::CanonicalForm::FOUR_IDENT);
|
|
}
|
|
return Twine(OffloadKind + "-" + NormalizedTriple + "-" + 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();
|
|
|
|
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.
|
|
std::string ModifiedContent;
|
|
if (Content.size() == 1u && Content.front() == 0) {
|
|
auto HostBundleOrErr = getHostBundle(
|
|
StringRef(Input.getBufferStart(), Input.getBufferSize()));
|
|
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();
|
|
}
|
|
|
|
Expected<std::string> getHostBundle(StringRef Input) {
|
|
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);
|
|
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()));
|
|
}
|
|
|
|
// Use packed structs to avoid padding, such that the structs map the serialized
|
|
// format.
|
|
LLVM_PACKED_START
|
|
union RawCompressedBundleHeader {
|
|
struct CommonFields {
|
|
uint32_t Magic;
|
|
uint16_t Version;
|
|
uint16_t Method;
|
|
};
|
|
|
|
struct V1Header {
|
|
CommonFields Common;
|
|
uint32_t UncompressedFileSize;
|
|
uint64_t Hash;
|
|
};
|
|
|
|
struct V2Header {
|
|
CommonFields Common;
|
|
uint32_t FileSize;
|
|
uint32_t UncompressedFileSize;
|
|
uint64_t Hash;
|
|
};
|
|
|
|
struct V3Header {
|
|
CommonFields Common;
|
|
uint64_t FileSize;
|
|
uint64_t UncompressedFileSize;
|
|
uint64_t Hash;
|
|
};
|
|
|
|
CommonFields Common;
|
|
V1Header V1;
|
|
V2Header V2;
|
|
V3Header V3;
|
|
};
|
|
LLVM_PACKED_END
|
|
|
|
// Helper method to get header size based on version
|
|
static size_t getHeaderSize(uint16_t Version) {
|
|
switch (Version) {
|
|
case 1:
|
|
return sizeof(RawCompressedBundleHeader::V1Header);
|
|
case 2:
|
|
return sizeof(RawCompressedBundleHeader::V2Header);
|
|
case 3:
|
|
return sizeof(RawCompressedBundleHeader::V3Header);
|
|
default:
|
|
llvm_unreachable("Unsupported version");
|
|
}
|
|
}
|
|
|
|
Expected<CompressedOffloadBundle::CompressedBundleHeader>
|
|
CompressedOffloadBundle::CompressedBundleHeader::tryParse(StringRef Blob) {
|
|
assert(Blob.size() >= sizeof(RawCompressedBundleHeader::CommonFields));
|
|
assert(llvm::identify_magic(Blob) ==
|
|
llvm::file_magic::offload_bundle_compressed);
|
|
|
|
RawCompressedBundleHeader Header;
|
|
memcpy(&Header, Blob.data(), std::min(Blob.size(), sizeof(Header)));
|
|
|
|
CompressedBundleHeader Normalized;
|
|
Normalized.Version = Header.Common.Version;
|
|
|
|
size_t RequiredSize = getHeaderSize(Normalized.Version);
|
|
if (Blob.size() < RequiredSize)
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Compressed bundle header size too small");
|
|
|
|
switch (Normalized.Version) {
|
|
case 1:
|
|
Normalized.UncompressedFileSize = Header.V1.UncompressedFileSize;
|
|
Normalized.Hash = Header.V1.Hash;
|
|
break;
|
|
case 2:
|
|
Normalized.FileSize = Header.V2.FileSize;
|
|
Normalized.UncompressedFileSize = Header.V2.UncompressedFileSize;
|
|
Normalized.Hash = Header.V2.Hash;
|
|
break;
|
|
case 3:
|
|
Normalized.FileSize = Header.V3.FileSize;
|
|
Normalized.UncompressedFileSize = Header.V3.UncompressedFileSize;
|
|
Normalized.Hash = Header.V3.Hash;
|
|
break;
|
|
default:
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Unknown compressed bundle version");
|
|
}
|
|
|
|
// Determine compression format
|
|
switch (Header.Common.Method) {
|
|
case static_cast<uint16_t>(compression::Format::Zlib):
|
|
case static_cast<uint16_t>(compression::Format::Zstd):
|
|
Normalized.CompressionFormat =
|
|
static_cast<compression::Format>(Header.Common.Method);
|
|
break;
|
|
default:
|
|
return createStringError(inconvertibleErrorCode(),
|
|
"Unknown compressing method");
|
|
}
|
|
|
|
return Normalized;
|
|
}
|
|
|
|
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() < sizeof(RawCompressedBundleHeader::CommonFields))
|
|
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);
|
|
}
|
|
|
|
Expected<CompressedBundleHeader> HeaderOrErr =
|
|
CompressedBundleHeader::tryParse(Blob);
|
|
if (!HeaderOrErr)
|
|
return HeaderOrErr.takeError();
|
|
|
|
const CompressedBundleHeader &Normalized = *HeaderOrErr;
|
|
unsigned ThisVersion = Normalized.Version;
|
|
size_t HeaderSize = getHeaderSize(ThisVersion);
|
|
|
|
llvm::compression::Format CompressionFormat = Normalized.CompressionFormat;
|
|
|
|
size_t TotalFileSize = Normalized.FileSize.value_or(0);
|
|
size_t UncompressedSize = Normalized.UncompressedFileSize;
|
|
auto StoredHash = Normalized.Hash;
|
|
|
|
llvm::Timer DecompressTimer("Decompression Timer", "Decompression time",
|
|
*ClangOffloadBundlerTimerGroup);
|
|
if (Verbose)
|
|
DecompressTimer.startTimer();
|
|
|
|
SmallVector<uint8_t, 0> DecompressedData;
|
|
StringRef CompressedData = Blob.substr(HeaderSize);
|
|
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) {
|
|
if (!checkOffloadBundleID(Triple))
|
|
return createStringError(errc::invalid_argument,
|
|
"invalid bundle id from bundle config");
|
|
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());
|
|
if (!checkOffloadBundleID(CurTriple))
|
|
return createStringError(errc::invalid_argument,
|
|
"invalid bundle id read from the bundle");
|
|
|
|
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.
|
|
// We don't need to check E.getKey() here through checkOffloadBundleID
|
|
// because the entire WorkList has been checked above.
|
|
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;
|
|
if (!checkOffloadBundleID(CodeObject)) {
|
|
return createStringError(errc::invalid_argument,
|
|
"Invalid bundle id read from code object");
|
|
}
|
|
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();
|
|
}
|
|
|
|
bool clang::checkOffloadBundleID(const llvm::StringRef Str) {
|
|
// <kind>-<triple>[-<target id>[:target features]]
|
|
// <triple> := <arch>-<vendor>-<os>-<env>
|
|
SmallVector<StringRef, 6> Components;
|
|
Str.split(Components, '-', /*MaxSplit=*/5);
|
|
return Components.size() == 5 || Components.size() == 6;
|
|
}
|