CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
327 lines
12 KiB
C++
327 lines
12 KiB
C++
//===-- 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));
|
|
|
|
static cl::opt<bool> AllowMissingPackages(
|
|
"allow-missing-packages",
|
|
cl::desc("Create empty files if packages are missing when unpackaging.\n"),
|
|
cl::init(false), 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();
|
|
// If the user lists a .so file on the command line for the program
|
|
// that invokes this one (probably clang), it may contain offload
|
|
// binary sections that resemble those in an object file. However,
|
|
// there is no late binding/shared object support on the target side
|
|
// (i.e. you cannot define a target function in a shared object and
|
|
// call it from a target region in the main program), and we don't want
|
|
// to *early* bind target regions in a shared object either. So,
|
|
// ignore shared objects here.
|
|
if (identify_magic(Binary->getImage()) == file_magic::elf_shared_object)
|
|
continue;
|
|
// We handle the 'file' and 'kind' 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);
|
|
return Binary->getString(Key) == Value;
|
|
});
|
|
if (Match)
|
|
Extracted.push_back(Binary);
|
|
}
|
|
|
|
if (Extracted.empty()) {
|
|
if (AllowMissingPackages)
|
|
if (Error E = writeFile(Args["file"], StringRef()))
|
|
return E;
|
|
|
|
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;
|
|
}
|