blob: f3099f43147af0e2586ae6e330c72c7ef11adae1 [file]
//===-- llvm-offload-binary.cpp - offload binary management utility -------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This tool takes several device object files and bundles them into a single
// binary image using a custom binary format. This is intended to be used to
// embed many device files into an application to create a fat binary. It also
// supports extracting these files from a known location.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/StringExtras.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Object/ArchiveWriter.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Object/OffloadBinary.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/StringSaver.h"
#include "llvm/Support/WithColor.h"
using namespace llvm;
using namespace llvm::object;
static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
static cl::OptionCategory OffloadBinaryCategory("llvm-offload-binary options");
static cl::opt<std::string> OutputFile("o", cl::desc("Write output to <file>."),
cl::value_desc("file"),
cl::cat(OffloadBinaryCategory));
static cl::opt<std::string> InputFile(cl::Positional,
cl::desc("Extract from <file>."),
cl::value_desc("file"),
cl::cat(OffloadBinaryCategory));
static cl::list<std::string>
DeviceImages("image",
cl::desc("List of key and value arguments. Required keywords "
"are 'file' and 'triple'."),
cl::value_desc("<key>=<value>,..."),
cl::cat(OffloadBinaryCategory));
static cl::opt<bool>
CreateArchive("archive",
cl::desc("Write extracted files to a static archive"),
cl::cat(OffloadBinaryCategory));
/// Path of the current binary.
static const char *PackagerExecutable;
// Get a map containing all the arguments for the image. Repeated arguments will
// be placed in a comma separated list.
static DenseMap<StringRef, StringRef> getImageArguments(StringRef Image,
StringSaver &Saver) {
DenseMap<StringRef, StringRef> Args;
for (StringRef Arg : llvm::split(Image, ",")) {
auto [Key, Value] = Arg.split("=");
auto [It, Inserted] = Args.try_emplace(Key, Value);
if (!Inserted)
It->second = Saver.save(It->second + "," + Value);
}
return Args;
}
static Error writeFile(StringRef Filename, StringRef Data) {
Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
FileOutputBuffer::create(Filename, Data.size());
if (!OutputOrErr)
return OutputOrErr.takeError();
std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
llvm::copy(Data, Output->getBufferStart());
if (Error E = Output->commit())
return E;
return Error::success();
}
static Error bundleImages() {
SmallVector<OffloadBinary::OffloadingImage> AllImages;
for (StringRef Image : DeviceImages) {
BumpPtrAllocator Alloc;
StringSaver Saver(Alloc);
DenseMap<StringRef, StringRef> Args = getImageArguments(Image, Saver);
if (!Args.count("file"))
return createStringError(inconvertibleErrorCode(),
"'file' is a required image arguments");
// Permit using multiple instances of `file` in a single string.
for (auto &File : llvm::split(Args["file"], ",")) {
OffloadBinary::OffloadingImage ImageBinary{};
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ObjectOrErr =
llvm::MemoryBuffer::getFileOrSTDIN(File);
if (std::error_code EC = ObjectOrErr.getError())
return errorCodeToError(EC);
// Clang uses the '.o' suffix for LTO bitcode.
if (identify_magic((*ObjectOrErr)->getBuffer()) == file_magic::bitcode)
ImageBinary.TheImageKind = object::IMG_Bitcode;
else if (sys::path::has_extension(File))
ImageBinary.TheImageKind =
getImageKind(sys::path::extension(File).drop_front());
else
ImageBinary.TheImageKind = IMG_None;
ImageBinary.Image = std::move(*ObjectOrErr);
for (const auto &[Key, Value] : Args) {
if (Key == "kind") {
ImageBinary.TheOffloadKind = getOffloadKind(Value);
} else if (Key != "file") {
ImageBinary.StringData[Key] = Value;
}
}
AllImages.emplace_back(std::move(ImageBinary));
}
}
SmallString<0> Buffer = OffloadBinary::write(AllImages);
if (Buffer.size() % OffloadBinary::getAlignment() != 0)
return createStringError(inconvertibleErrorCode(),
"Offload binary has invalid size alignment");
if (Error E = writeFile(OutputFile, StringRef(Buffer.data(), Buffer.size())))
return E;
return Error::success();
}
// Extract a single OffloadBinary, recursively handling nested OffloadBinaries.
static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
uint64_t &Idx, StringSaver &Saver) {
StringRef ImageData = Binary->getImage();
// Check if the image contains a nested OffloadBinary.
if (identify_magic(ImageData) == file_magic::offload_binary) {
// Parse nested OffloadBinary.
MemoryBufferRef InnerBuffer(ImageData, "nested-offload-binary");
SmallVector<OffloadFile> InnerBinaries;
if (Error Err = extractOffloadBinaries(InnerBuffer, InnerBinaries))
return Err;
// Recursively extract each nested binary.
for (const auto &InnerBinary : InnerBinaries) {
if (Error E =
extractBinary(InnerBinary.getBinary(), InputFile, Idx, Saver))
return E;
}
return Error::success();
}
// Base case: extract the actual device image.
std::string Filename;
raw_string_ostream SS(Filename);
SS << sys::path::stem(InputFile) << "-" << Binary->getTriple();
StringRef Arch = Binary->getArch();
if (!Arch.empty())
SS << "-" << Arch;
SS << "." << Idx++ << "." << getImageKindName(Binary->getImageKind());
if (Error E = writeFile(Saver.save(Filename), ImageData))
return E;
outs() << "Extracted: " << Filename << "\n";
return Error::success();
}
static Error unbundleImages() {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
MemoryBuffer::getFileOrSTDIN(InputFile);
if (std::error_code EC = BufferOrErr.getError())
return createFileError(InputFile, EC);
std::unique_ptr<MemoryBuffer> Buffer = std::move(*BufferOrErr);
// This data can be misaligned if extracted from an archive.
if (!isAddrAligned(Align(OffloadBinary::getAlignment()),
Buffer->getBufferStart()))
Buffer = MemoryBuffer::getMemBufferCopy(Buffer->getBuffer(),
Buffer->getBufferIdentifier());
SmallVector<OffloadFile> Binaries;
if (Error Err = extractOffloadBinaries(*Buffer, Binaries))
return Err;
// If no filters specified, extract all images.
if (DeviceImages.empty()) {
BumpPtrAllocator Alloc;
StringSaver Saver(Alloc);
uint64_t Idx = 0;
for (const OffloadFile &File : Binaries) {
if (Error E = extractBinary(File.getBinary(), InputFile, Idx, Saver))
return E;
}
return Error::success();
}
// Try to extract each device image specified by the user from the input file.
for (StringRef Image : DeviceImages) {
BumpPtrAllocator Alloc;
StringSaver Saver(Alloc);
auto Args = getImageArguments(Image, Saver);
SmallVector<const OffloadBinary *> Extracted;
for (const OffloadFile &File : Binaries) {
const auto *Binary = File.getBinary();
// We handle the 'file', 'kind', and 'member' identifiers differently.
bool Match = llvm::all_of(Args, [&](auto &Arg) {
const auto [Key, Value] = Arg;
if (Key == "file")
return true;
if (Key == "kind")
return Binary->getOffloadKind() == getOffloadKind(Value);
if (Key == "member")
return sys::path::filename(
Binary->getMemoryBufferRef().getBufferIdentifier()) ==
Value;
return Binary->getString(Key) == Value;
});
if (Match)
Extracted.push_back(Binary);
}
if (Extracted.empty())
continue;
if (CreateArchive) {
if (!Args.count("file"))
return createStringError(inconvertibleErrorCode(),
"Image must have a 'file' argument.");
SmallVector<NewArchiveMember> Members;
for (const OffloadBinary *Binary : Extracted)
Members.emplace_back(MemoryBufferRef(
Binary->getImage(),
Binary->getMemoryBufferRef().getBufferIdentifier()));
if (Error E = writeArchive(
Args["file"], Members, SymtabWritingMode::NormalSymtab,
Archive::getDefaultKind(), true, false, nullptr))
return E;
} else if (auto It = Args.find("file"); It != Args.end()) {
if (Extracted.size() > 1)
WithColor::warning(errs(), PackagerExecutable)
<< "Multiple inputs match to a single file, '" << It->second
<< "'\n";
if (Error E = writeFile(It->second, Extracted.back()->getImage()))
return E;
} else {
uint64_t Idx = 0;
for (const OffloadBinary *Binary : Extracted) {
if (Error E = extractBinary(Binary, InputFile, Idx, Saver))
return E;
}
}
}
return Error::success();
}
int main(int argc, const char **argv) {
sys::PrintStackTraceOnErrorSignal(argv[0]);
cl::HideUnrelatedOptions(OffloadBinaryCategory);
cl::ParseCommandLineOptions(
argc, argv,
"A utility for bundling several object files into a single binary.\n"
"The output binary can then be embedded into the host section table\n"
"to create a fatbinary containing offloading code.\n");
if (sys::path::stem(argv[0]).ends_with("clang-offload-packager"))
WithColor::warning(errs(), PackagerExecutable)
<< "'clang-offload-packager' is deprecated. Use 'llvm-offload-binary' "
"instead.\n";
if (Help || (OutputFile.empty() && InputFile.empty())) {
cl::PrintHelpMessage();
return EXIT_SUCCESS;
}
PackagerExecutable = argv[0];
auto reportError = [argv](Error E) {
logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0]));
return EXIT_FAILURE;
};
if (!InputFile.empty() && !OutputFile.empty())
return reportError(
createStringError(inconvertibleErrorCode(),
"Packaging to an output file and extracting from an "
"input file are mutually exclusive."));
if (!OutputFile.empty()) {
if (Error Err = bundleImages())
return reportError(std::move(Err));
} else if (!InputFile.empty()) {
if (Error Err = unbundleImages())
return reportError(std::move(Err));
}
return EXIT_SUCCESS;
}