[Offloading] Offload Binary Format V2: Support Multiple Entries (#169425)
This PR updates the OffloadBinary format from version 1 to version 2,
enabling support for multiple offloading entries in a single binary.
This allows combining multiple device images into a single binary with
common global metadata while maintaining backwards compatibility with
version 1 binaries.
# Key Changes
## Binary Format Enhancements
**Version 2 Format Changes:**
- Changed from single-entry to multi-entry design
- Updated `Header` structure:
- Renamed `EntryOffset` → `EntriesOffset` (offset to entries array)
- Renamed `EntrySize` → `EntriesCount` (number of entries)
- Added `StringEntry::ValueSize` field to support explicit string value
sizes (enables non-null-terminated strings)
- Introduced `OffloadEntryFlags` enum with `OIF_Metadata` flag for
metadata-only entries (entries without binary images)
**API Changes:**
- `OffloadBinary::create()` now returns
`Expected<SmallVector<std::unique_ptr<OffloadBinary>>>` instead of
single binary
- Added optional `Index` parameter to extract specific entry:
`create(Buffer, std::optional<uint64_t> Index)`
- `OffloadBinary::write()` now accepts `ArrayRef<OffloadingImage>`
instead of single image
- Added `OffloadBinary::extractHeader()` for header extraction
**Memory Management:**
- Implemented `SharedMemoryBuffer` class to enable memory sharing across
multiple `OffloadBinary` instances from the same file
- Multiple entries from a single serialized binary share the underlying
buffer
## Testing
**Unit Tests (`unittests/Object/OffloadingTest.cpp`):**
- `checkMultiEntryBinaryExtraction`: Tests extracting all entries from a
multi-entry binary
- `checkIndexBasedExtraction`: Tests extracting specific entries by
index, including out-of-bounds validation
- `checkEdgeCases`: Tests edge cases including:
- Empty string metadata
- Empty image data
- Large string values (4KB)
**Other Tests:**
- Updated `test/ObjectYAML/Offload/multiple_members.yaml` to include
metadata-only entry
---------
Co-authored-by: Joseph Huber <huberjn@outlook.com>
This commit is contained in:
parent
10910771e8
commit
4d27530c69
@ -25,7 +25,7 @@
|
||||
// OPENMP-REL: @.omp_offloading.device_image = internal unnamed_addr constant [[[SIZE:[0-9]+]] x i8] c"\10\FF\10\AD{{.*}}", section ".llvm.offloading.relocatable", align 8
|
||||
|
||||
// OPENMP: @.omp_offloading.device_image = internal unnamed_addr constant [[[SIZE:[0-9]+]] x i8] c"\10\FF\10\AD{{.*}}", section ".llvm.offloading", align 8
|
||||
// OPENMP-NEXT: @.omp_offloading.device_images = internal unnamed_addr constant [1 x %__tgt_device_image] [%__tgt_device_image { ptr getelementptr ([[[BEGIN:[0-9]+]] x i8], ptr @.omp_offloading.device_image, i64 0, i64 144), ptr getelementptr ([[[END:[0-9]+]] x i8], ptr @.omp_offloading.device_image, i64 0, i64 144), ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }]
|
||||
// OPENMP-NEXT: @.omp_offloading.device_images = internal unnamed_addr constant [1 x %__tgt_device_image] [%__tgt_device_image { ptr getelementptr ([[[IMG_OFF:[0-9]+]] x i8], ptr @.omp_offloading.device_image, i64 0, i64 [[IMG_OFF]]), ptr getelementptr ([[[IMG_OFF]] x i8], ptr @.omp_offloading.device_image, i64 0, i64 [[IMG_OFF]]), ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }]
|
||||
// OPENMP-NEXT: @.omp_offloading.descriptor = internal constant %__tgt_bin_desc { i32 1, ptr @.omp_offloading.device_images, ptr @__start_llvm_offload_entries, ptr @__stop_llvm_offload_entries }
|
||||
// OPENMP-NEXT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @.omp_offloading.descriptor_reg, ptr null }]
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file contains the binary format used for budingling device metadata with
|
||||
// This file contains the binary format used for bundling device metadata with
|
||||
// an associated device image. The data can then be stored inside a host object
|
||||
// file to create a fat binary and read by the linker. This is intended to be a
|
||||
// thin wrapper around the image itself. If this format becomes sufficiently
|
||||
@ -52,6 +52,13 @@ enum ImageKind : uint16_t {
|
||||
IMG_LAST,
|
||||
};
|
||||
|
||||
/// Flags associated with the Entry.
|
||||
enum OffloadEntryFlags : uint32_t {
|
||||
OIF_None = 0,
|
||||
// Entry doesn't contain an image. Used to keep metadata only entries.
|
||||
OIF_Metadata = (1 << 0),
|
||||
};
|
||||
|
||||
/// A simple binary serialization of an offloading file. We use this format to
|
||||
/// embed the offloading image into the host executable so it can be extracted
|
||||
/// and used by the linker.
|
||||
@ -67,7 +74,7 @@ public:
|
||||
using string_iterator_range = iterator_range<string_iterator>;
|
||||
|
||||
/// The current version of the binary used for backwards compatibility.
|
||||
static const uint32_t Version = 1;
|
||||
static const uint32_t Version = 2;
|
||||
|
||||
/// The offloading metadata that will be serialized to a memory buffer.
|
||||
struct OffloadingImage {
|
||||
@ -78,12 +85,57 @@ public:
|
||||
std::unique_ptr<MemoryBuffer> Image;
|
||||
};
|
||||
|
||||
/// Attempt to parse the offloading binary stored in \p Data.
|
||||
LLVM_ABI static Expected<std::unique_ptr<OffloadBinary>>
|
||||
create(MemoryBufferRef);
|
||||
struct Header {
|
||||
uint8_t Magic[4] = {0x10, 0xFF, 0x10, 0xAD}; // 0x10FF10AD magic bytes.
|
||||
uint32_t Version = OffloadBinary::Version; // Version identifier.
|
||||
uint64_t Size; // Size in bytes of this entire binary.
|
||||
uint64_t EntriesOffset; // Offset in bytes to the start of entries block.
|
||||
uint64_t EntriesCount; // Number of metadata entries in the binary.
|
||||
};
|
||||
|
||||
/// Serialize the contents of \p File to a binary buffer to be read later.
|
||||
LLVM_ABI static SmallString<0> write(const OffloadingImage &);
|
||||
struct Entry {
|
||||
ImageKind TheImageKind; // The kind of the image stored.
|
||||
OffloadKind TheOffloadKind; // The producer of this image.
|
||||
uint32_t Flags; // Additional flags associated with the entry.
|
||||
uint64_t StringOffset; // Offset in bytes to the string map.
|
||||
uint64_t NumStrings; // Number of entries in the string map.
|
||||
uint64_t ImageOffset; // Offset in bytes of the actual binary image.
|
||||
uint64_t ImageSize; // Size in bytes of the binary image.
|
||||
};
|
||||
|
||||
struct StringEntry {
|
||||
uint64_t KeyOffset;
|
||||
uint64_t ValueOffset;
|
||||
uint64_t ValueSize; // Size of the value in bytes.
|
||||
};
|
||||
|
||||
struct StringEntryV1 {
|
||||
uint64_t KeyOffset;
|
||||
uint64_t ValueOffset;
|
||||
};
|
||||
|
||||
/// Attempt to extract and validate the header from the offloading binary in
|
||||
/// \p Buf.
|
||||
LLVM_ABI
|
||||
static Expected<const Header *> extractHeader(MemoryBufferRef Buf);
|
||||
|
||||
/// Attempt to parse the offloading binary stored in \p Buf.
|
||||
/// For version 1 binaries, always returns a single OffloadBinary.
|
||||
/// For version 2+ binaries:
|
||||
/// - If \p Index is provided, returns the OffloadBinary at that index.
|
||||
/// - If \p Index is std::nullopt, returns all OffloadBinary entries.
|
||||
/// \param Buf The memory buffer containing the offload binary.
|
||||
/// \param Index Optional index to select a specific entry. If not provided,
|
||||
/// all entries are returned (version 2+ only).
|
||||
/// \returns An array of unique pointers to OffloadBinary objects, or an
|
||||
/// error.
|
||||
LLVM_ABI static Expected<SmallVector<std::unique_ptr<OffloadBinary>>>
|
||||
create(MemoryBufferRef Buf, std::optional<uint64_t> Index = std::nullopt);
|
||||
|
||||
/// Serialize the contents of \p OffloadingData to a binary buffer to be read
|
||||
/// later.
|
||||
LLVM_ABI static SmallString<0>
|
||||
write(ArrayRef<OffloadingImage> OffloadingData);
|
||||
|
||||
static uint64_t getAlignment() { return 8; }
|
||||
|
||||
@ -92,6 +144,7 @@ public:
|
||||
uint32_t getVersion() const { return TheHeader->Version; }
|
||||
uint32_t getFlags() const { return TheEntry->Flags; }
|
||||
uint64_t getSize() const { return TheHeader->Size; }
|
||||
uint64_t getIndex() const { return Index; }
|
||||
|
||||
StringRef getTriple() const { return getString("triple"); }
|
||||
StringRef getArch() const { return getString("arch"); }
|
||||
@ -106,39 +159,29 @@ public:
|
||||
|
||||
static bool classof(const Binary *V) { return V->isOffloadFile(); }
|
||||
|
||||
struct Header {
|
||||
uint8_t Magic[4] = {0x10, 0xFF, 0x10, 0xAD}; // 0x10FF10AD magic bytes.
|
||||
uint32_t Version = OffloadBinary::Version; // Version identifier.
|
||||
uint64_t Size; // Size in bytes of this entire binary.
|
||||
uint64_t EntryOffset; // Offset of the metadata entry in bytes.
|
||||
uint64_t EntrySize; // Size of the metadata entry in bytes.
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
ImageKind TheImageKind; // The kind of the image stored.
|
||||
OffloadKind TheOffloadKind; // The producer of this image.
|
||||
uint32_t Flags; // Additional flags associated with the image.
|
||||
uint64_t StringOffset; // Offset in bytes to the string map.
|
||||
uint64_t NumStrings; // Number of entries in the string map.
|
||||
uint64_t ImageOffset; // Offset in bytes of the actual binary image.
|
||||
uint64_t ImageSize; // Size in bytes of the binary image.
|
||||
};
|
||||
|
||||
struct StringEntry {
|
||||
uint64_t KeyOffset;
|
||||
uint64_t ValueOffset;
|
||||
};
|
||||
|
||||
private:
|
||||
OffloadBinary(MemoryBufferRef Source, const Header *TheHeader,
|
||||
const Entry *TheEntry)
|
||||
const Entry *TheEntry, const uint64_t Index = 0)
|
||||
: Binary(Binary::ID_Offload, Source), Buffer(Source.getBufferStart()),
|
||||
TheHeader(TheHeader), TheEntry(TheEntry) {
|
||||
const StringEntry *StringMapBegin =
|
||||
reinterpret_cast<const StringEntry *>(&Buffer[TheEntry->StringOffset]);
|
||||
TheHeader(TheHeader), TheEntry(TheEntry), Index(Index) {
|
||||
// StringEntryV1 and StringEntry have ABI compatible Key/ValueOffset fields,
|
||||
// but different sizes, so we need to manually calculate offset.
|
||||
const char *StringMapBegin = &Buffer[TheEntry->StringOffset];
|
||||
const size_t StringEntrySize =
|
||||
TheHeader->Version == 1 ? sizeof(StringEntryV1) : sizeof(StringEntry);
|
||||
for (uint64_t I = 0, E = TheEntry->NumStrings; I != E; ++I) {
|
||||
StringRef Key = &Buffer[StringMapBegin[I].KeyOffset];
|
||||
StringData[Key] = &Buffer[StringMapBegin[I].ValueOffset];
|
||||
const char *StringEntryPtr = StringMapBegin + I * StringEntrySize;
|
||||
const StringEntryV1 *EntryV1 =
|
||||
reinterpret_cast<const StringEntryV1 *>(StringEntryPtr);
|
||||
StringRef Key = &Buffer[EntryV1->KeyOffset];
|
||||
if (TheHeader->Version == 1) {
|
||||
StringData[Key] = &Buffer[EntryV1->ValueOffset];
|
||||
} else {
|
||||
const StringEntry *Entry =
|
||||
reinterpret_cast<const StringEntry *>(StringEntryPtr);
|
||||
StringData[Key] =
|
||||
StringRef(&Buffer[Entry->ValueOffset], Entry->ValueSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,10 +195,13 @@ private:
|
||||
const Header *TheHeader;
|
||||
/// Location of the metadata entries within the binary.
|
||||
const Entry *TheEntry;
|
||||
/// Index of the entry in the list of entries serialized in the Buffer.
|
||||
const uint64_t Index;
|
||||
};
|
||||
|
||||
/// A class to contain the binary information for a single OffloadBinary that
|
||||
/// owns its memory.
|
||||
/// A class to contain the binary information for a single OffloadBinary.
|
||||
/// Memory is shared between multiple OffloadBinary instances read from
|
||||
/// the single serialized offload binary.
|
||||
class OffloadFile : public OwningBinary<OffloadBinary> {
|
||||
public:
|
||||
using TargetID = std::pair<StringRef, StringRef>;
|
||||
@ -171,11 +217,12 @@ public:
|
||||
getBinary()->getMemoryBufferRef().getBufferIdentifier());
|
||||
|
||||
// This parsing should never fail because it has already been parsed.
|
||||
auto NewBinaryOrErr = OffloadBinary::create(*Buffer);
|
||||
auto NewBinaryOrErr =
|
||||
OffloadBinary::create(*Buffer, getBinary()->getIndex());
|
||||
assert(NewBinaryOrErr && "Failed to parse a copy of the binary?");
|
||||
if (!NewBinaryOrErr)
|
||||
llvm::consumeError(NewBinaryOrErr.takeError());
|
||||
return OffloadFile(std::move(*NewBinaryOrErr), std::move(Buffer));
|
||||
return OffloadFile(std::move((*NewBinaryOrErr)[0]), std::move(Buffer));
|
||||
}
|
||||
|
||||
/// We use the Triple and Architecture pair to group linker inputs together.
|
||||
|
||||
@ -39,8 +39,8 @@ struct Binary {
|
||||
|
||||
std::optional<uint32_t> Version;
|
||||
std::optional<uint64_t> Size;
|
||||
std::optional<uint64_t> EntryOffset;
|
||||
std::optional<uint64_t> EntrySize;
|
||||
std::optional<uint64_t> EntriesOffset;
|
||||
std::optional<uint64_t> EntriesCount;
|
||||
std::vector<Member> Members;
|
||||
};
|
||||
|
||||
|
||||
@ -161,7 +161,7 @@ GlobalVariable *createBinDesc(Module &M, ArrayRef<ArrayRef<char>> Bufs,
|
||||
Binary.bytes_begin());
|
||||
const auto *Entry =
|
||||
reinterpret_cast<const object::OffloadBinary::Entry *>(
|
||||
Binary.bytes_begin() + Header->EntryOffset);
|
||||
Binary.bytes_begin() + Header->EntriesOffset);
|
||||
BeginOffset = Entry->ImageOffset;
|
||||
EndOffset = Entry->ImageOffset + Entry->ImageSize;
|
||||
}
|
||||
|
||||
@ -93,8 +93,12 @@ Expected<std::unique_ptr<Binary>> object::createBinary(MemoryBufferRef Buffer,
|
||||
case file_magic::spirv_object:
|
||||
// Unrecognized object file format.
|
||||
return errorCodeToError(object_error::invalid_file_type);
|
||||
case file_magic::offload_binary:
|
||||
return OffloadBinary::create(Buffer);
|
||||
case file_magic::offload_binary: {
|
||||
auto OffloadBinaryOrErr = OffloadBinary::create(Buffer);
|
||||
if (!OffloadBinaryOrErr)
|
||||
return OffloadBinaryOrErr.takeError();
|
||||
return std::move((*OffloadBinaryOrErr)[0]);
|
||||
}
|
||||
case file_magic::minidump:
|
||||
return MinidumpFile::create(Buffer);
|
||||
case file_magic::tapi_file:
|
||||
|
||||
@ -28,6 +28,26 @@ using namespace llvm::object;
|
||||
|
||||
namespace {
|
||||
|
||||
/// A MemoryBuffer that shares ownership of the underlying memory.
|
||||
/// This allows multiple OffloadBinary instances to share the same buffer.
|
||||
class SharedMemoryBuffer : public MemoryBuffer {
|
||||
public:
|
||||
SharedMemoryBuffer(std::shared_ptr<MemoryBuffer> Buf)
|
||||
: SharedBuf(std::move(Buf)) {
|
||||
init(SharedBuf->getBufferStart(), SharedBuf->getBufferEnd(),
|
||||
/*RequiresNullTerminator=*/false);
|
||||
}
|
||||
|
||||
BufferKind getBufferKind() const override { return MemoryBuffer_Malloc; }
|
||||
|
||||
StringRef getBufferIdentifier() const override {
|
||||
return SharedBuf->getBufferIdentifier();
|
||||
}
|
||||
|
||||
private:
|
||||
const std::shared_ptr<MemoryBuffer> SharedBuf;
|
||||
};
|
||||
|
||||
/// Attempts to extract all the embedded device images contained inside the
|
||||
/// buffer \p Contents. The buffer is expected to contain a valid offloading
|
||||
/// binary format.
|
||||
@ -35,7 +55,7 @@ Error extractOffloadFiles(MemoryBufferRef Contents,
|
||||
SmallVectorImpl<OffloadFile> &Binaries) {
|
||||
uint64_t Offset = 0;
|
||||
// There could be multiple offloading binaries stored at this section.
|
||||
while (Offset < Contents.getBuffer().size()) {
|
||||
while (Offset < Contents.getBufferSize()) {
|
||||
std::unique_ptr<MemoryBuffer> Buffer =
|
||||
MemoryBuffer::getMemBuffer(Contents.getBuffer().drop_front(Offset), "",
|
||||
/*RequiresNullTerminator*/ false);
|
||||
@ -43,21 +63,32 @@ Error extractOffloadFiles(MemoryBufferRef Contents,
|
||||
Buffer->getBufferStart()))
|
||||
Buffer = MemoryBuffer::getMemBufferCopy(Buffer->getBuffer(),
|
||||
Buffer->getBufferIdentifier());
|
||||
auto BinaryOrErr = OffloadBinary::create(*Buffer);
|
||||
if (!BinaryOrErr)
|
||||
return BinaryOrErr.takeError();
|
||||
OffloadBinary &Binary = **BinaryOrErr;
|
||||
|
||||
// Create a new owned binary with a copy of the original memory.
|
||||
auto HeaderOrErr = OffloadBinary::extractHeader(*Buffer);
|
||||
if (!HeaderOrErr)
|
||||
return HeaderOrErr.takeError();
|
||||
const OffloadBinary::Header *Header = *HeaderOrErr;
|
||||
|
||||
// Create a copy of original memory containing only the current binary.
|
||||
std::unique_ptr<MemoryBuffer> BufferCopy = MemoryBuffer::getMemBufferCopy(
|
||||
Binary.getData().take_front(Binary.getSize()),
|
||||
Buffer->getBuffer().take_front(Header->Size),
|
||||
Contents.getBufferIdentifier());
|
||||
auto NewBinaryOrErr = OffloadBinary::create(*BufferCopy);
|
||||
if (!NewBinaryOrErr)
|
||||
return NewBinaryOrErr.takeError();
|
||||
Binaries.emplace_back(std::move(*NewBinaryOrErr), std::move(BufferCopy));
|
||||
|
||||
Offset += Binary.getSize();
|
||||
auto BinariesOrErr = OffloadBinary::create(*BufferCopy);
|
||||
if (!BinariesOrErr)
|
||||
return BinariesOrErr.takeError();
|
||||
|
||||
// Share ownership among multiple OffloadFiles.
|
||||
std::shared_ptr<MemoryBuffer> SharedBuffer =
|
||||
std::shared_ptr<MemoryBuffer>(std::move(BufferCopy));
|
||||
|
||||
for (auto &Binary : *BinariesOrErr) {
|
||||
std::unique_ptr<SharedMemoryBuffer> SharedBufferPtr =
|
||||
std::make_unique<SharedMemoryBuffer>(SharedBuffer);
|
||||
Binaries.emplace_back(std::move(Binary), std::move(SharedBufferPtr));
|
||||
}
|
||||
|
||||
Offset += Header->Size;
|
||||
}
|
||||
|
||||
return Error::success();
|
||||
@ -167,8 +198,8 @@ Error extractFromArchive(const Archive &Library,
|
||||
|
||||
} // namespace
|
||||
|
||||
Expected<std::unique_ptr<OffloadBinary>>
|
||||
OffloadBinary::create(MemoryBufferRef Buf) {
|
||||
Expected<const OffloadBinary::Header *>
|
||||
OffloadBinary::extractHeader(MemoryBufferRef Buf) {
|
||||
if (Buf.getBufferSize() < sizeof(Header) + sizeof(Entry))
|
||||
return errorCodeToError(object_error::parse_failed);
|
||||
|
||||
@ -182,83 +213,146 @@ OffloadBinary::create(MemoryBufferRef Buf) {
|
||||
|
||||
const char *Start = Buf.getBufferStart();
|
||||
const Header *TheHeader = reinterpret_cast<const Header *>(Start);
|
||||
if (TheHeader->Version != OffloadBinary::Version)
|
||||
if (TheHeader->Version == 0 || TheHeader->Version > OffloadBinary::Version)
|
||||
return errorCodeToError(object_error::parse_failed);
|
||||
|
||||
if (TheHeader->Size > Buf.getBufferSize() ||
|
||||
TheHeader->Size < sizeof(Entry) || TheHeader->Size < sizeof(Header))
|
||||
return errorCodeToError(object_error::unexpected_eof);
|
||||
|
||||
if (TheHeader->EntryOffset > TheHeader->Size - sizeof(Entry) ||
|
||||
TheHeader->EntrySize > TheHeader->Size - sizeof(Header))
|
||||
uint64_t EntriesCount =
|
||||
(TheHeader->Version == 1) ? 1 : TheHeader->EntriesCount;
|
||||
uint64_t EntriesSize = sizeof(Entry) * EntriesCount;
|
||||
if (TheHeader->EntriesOffset > TheHeader->Size - EntriesSize ||
|
||||
EntriesSize > TheHeader->Size - sizeof(Header))
|
||||
return errorCodeToError(object_error::unexpected_eof);
|
||||
|
||||
const Entry *TheEntry =
|
||||
reinterpret_cast<const Entry *>(&Start[TheHeader->EntryOffset]);
|
||||
|
||||
if (TheEntry->ImageOffset > Buf.getBufferSize() ||
|
||||
TheEntry->StringOffset > Buf.getBufferSize() ||
|
||||
TheEntry->StringOffset + TheEntry->NumStrings * sizeof(StringEntry) >
|
||||
Buf.getBufferSize())
|
||||
return errorCodeToError(object_error::unexpected_eof);
|
||||
|
||||
return std::unique_ptr<OffloadBinary>(
|
||||
new OffloadBinary(Buf, TheHeader, TheEntry));
|
||||
return TheHeader;
|
||||
}
|
||||
|
||||
SmallString<0> OffloadBinary::write(const OffloadingImage &OffloadingData) {
|
||||
Expected<SmallVector<std::unique_ptr<OffloadBinary>>>
|
||||
OffloadBinary::create(MemoryBufferRef Buf, std::optional<uint64_t> Index) {
|
||||
auto HeaderOrErr = OffloadBinary::extractHeader(Buf);
|
||||
if (!HeaderOrErr)
|
||||
return HeaderOrErr.takeError();
|
||||
const Header *TheHeader = *HeaderOrErr;
|
||||
|
||||
const char *Start = Buf.getBufferStart();
|
||||
const Entry *Entries =
|
||||
reinterpret_cast<const Entry *>(&Start[TheHeader->EntriesOffset]);
|
||||
|
||||
auto validateEntry = [&](const Entry *TheEntry) -> Error {
|
||||
if (TheEntry->ImageOffset > Buf.getBufferSize() ||
|
||||
TheEntry->StringOffset > Buf.getBufferSize() ||
|
||||
TheEntry->StringOffset + TheEntry->NumStrings * sizeof(StringEntry) >
|
||||
Buf.getBufferSize())
|
||||
return errorCodeToError(object_error::unexpected_eof);
|
||||
return Error::success();
|
||||
};
|
||||
|
||||
SmallVector<std::unique_ptr<OffloadBinary>> Binaries;
|
||||
if (TheHeader->Version > 1 && Index.has_value()) {
|
||||
if (*Index >= TheHeader->EntriesCount)
|
||||
return errorCodeToError(object_error::parse_failed);
|
||||
const Entry *TheEntry = &Entries[*Index];
|
||||
if (auto Err = validateEntry(TheEntry))
|
||||
return std::move(Err);
|
||||
|
||||
Binaries.emplace_back(new OffloadBinary(Buf, TheHeader, TheEntry, *Index));
|
||||
return Binaries;
|
||||
}
|
||||
|
||||
uint64_t EntriesCount = TheHeader->Version == 1 ? 1 : TheHeader->EntriesCount;
|
||||
for (uint64_t I = 0; I < EntriesCount; ++I) {
|
||||
const Entry *TheEntry = &Entries[I];
|
||||
if (auto Err = validateEntry(TheEntry))
|
||||
return std::move(Err);
|
||||
|
||||
Binaries.emplace_back(new OffloadBinary(Buf, TheHeader, TheEntry, I));
|
||||
}
|
||||
|
||||
return Binaries;
|
||||
}
|
||||
|
||||
SmallString<0> OffloadBinary::write(ArrayRef<OffloadingImage> OffloadingData) {
|
||||
uint64_t EntriesCount = OffloadingData.size();
|
||||
assert(EntriesCount > 0 && "At least one offloading image is required");
|
||||
|
||||
// Create a null-terminated string table with all the used strings.
|
||||
// Also calculate total size of images.
|
||||
StringTableBuilder StrTab(StringTableBuilder::ELF);
|
||||
for (auto &KeyAndValue : OffloadingData.StringData) {
|
||||
StrTab.add(KeyAndValue.first);
|
||||
StrTab.add(KeyAndValue.second);
|
||||
uint64_t TotalStringEntries = 0;
|
||||
uint64_t TotalImagesSize = 0;
|
||||
for (const OffloadingImage &Img : OffloadingData) {
|
||||
for (auto &KeyAndValue : Img.StringData) {
|
||||
StrTab.add(KeyAndValue.first);
|
||||
StrTab.add(KeyAndValue.second);
|
||||
}
|
||||
TotalStringEntries += Img.StringData.size();
|
||||
TotalImagesSize += Img.Image->getBufferSize();
|
||||
}
|
||||
StrTab.finalize();
|
||||
|
||||
uint64_t StringEntrySize =
|
||||
sizeof(StringEntry) * OffloadingData.StringData.size();
|
||||
uint64_t StringEntrySize = sizeof(StringEntry) * TotalStringEntries;
|
||||
uint64_t EntriesSize = sizeof(Entry) * EntriesCount;
|
||||
uint64_t StrTabOffset = sizeof(Header) + EntriesSize + StringEntrySize;
|
||||
|
||||
// Make sure the image we're wrapping around is aligned as well.
|
||||
uint64_t BinaryDataSize = alignTo(sizeof(Header) + sizeof(Entry) +
|
||||
StringEntrySize + StrTab.getSize(),
|
||||
getAlignment());
|
||||
uint64_t BinaryDataSize =
|
||||
alignTo(StrTabOffset + StrTab.getSize(), getAlignment());
|
||||
|
||||
// Create the header and fill in the offsets. The entry will be directly
|
||||
// Create the header and fill in the offsets. The entries will be directly
|
||||
// placed after the header in memory. Align the size to the alignment of the
|
||||
// header so this can be placed contiguously in a single section.
|
||||
Header TheHeader;
|
||||
TheHeader.Size = alignTo(
|
||||
BinaryDataSize + OffloadingData.Image->getBufferSize(), getAlignment());
|
||||
TheHeader.EntryOffset = sizeof(Header);
|
||||
TheHeader.EntrySize = sizeof(Entry);
|
||||
|
||||
// Create the entry using the string table offsets. The string table will be
|
||||
// placed directly after the entry in memory, and the image after that.
|
||||
Entry TheEntry;
|
||||
TheEntry.TheImageKind = OffloadingData.TheImageKind;
|
||||
TheEntry.TheOffloadKind = OffloadingData.TheOffloadKind;
|
||||
TheEntry.Flags = OffloadingData.Flags;
|
||||
TheEntry.StringOffset = sizeof(Header) + sizeof(Entry);
|
||||
TheEntry.NumStrings = OffloadingData.StringData.size();
|
||||
|
||||
TheEntry.ImageOffset = BinaryDataSize;
|
||||
TheEntry.ImageSize = OffloadingData.Image->getBufferSize();
|
||||
TheHeader.Size = alignTo(BinaryDataSize + TotalImagesSize, getAlignment());
|
||||
TheHeader.EntriesOffset = sizeof(Header);
|
||||
TheHeader.EntriesCount = EntriesCount;
|
||||
|
||||
SmallString<0> Data;
|
||||
Data.reserve(TheHeader.Size);
|
||||
raw_svector_ostream OS(Data);
|
||||
OS << StringRef(reinterpret_cast<char *>(&TheHeader), sizeof(Header));
|
||||
OS << StringRef(reinterpret_cast<char *>(&TheEntry), sizeof(Entry));
|
||||
for (auto &KeyAndValue : OffloadingData.StringData) {
|
||||
uint64_t Offset = sizeof(Header) + sizeof(Entry) + StringEntrySize;
|
||||
StringEntry Map{Offset + StrTab.getOffset(KeyAndValue.first),
|
||||
Offset + StrTab.getOffset(KeyAndValue.second)};
|
||||
OS << StringRef(reinterpret_cast<char *>(&Map), sizeof(StringEntry));
|
||||
|
||||
// Create the entries using the string table offsets. The string table will be
|
||||
// placed directly after the set of entries in memory, and all the images are
|
||||
// after that.
|
||||
uint64_t StringEntryOffset = sizeof(Header) + EntriesSize;
|
||||
uint64_t ImageOffset = BinaryDataSize;
|
||||
for (const OffloadingImage &Img : OffloadingData) {
|
||||
Entry TheEntry;
|
||||
|
||||
TheEntry.TheImageKind = Img.TheImageKind;
|
||||
TheEntry.TheOffloadKind = Img.TheOffloadKind;
|
||||
TheEntry.Flags = Img.Flags;
|
||||
|
||||
TheEntry.StringOffset = StringEntryOffset;
|
||||
StringEntryOffset += sizeof(StringEntry) * Img.StringData.size();
|
||||
TheEntry.NumStrings = Img.StringData.size();
|
||||
|
||||
TheEntry.ImageOffset = ImageOffset;
|
||||
ImageOffset += Img.Image->getBufferSize();
|
||||
TheEntry.ImageSize = Img.Image->getBufferSize();
|
||||
|
||||
OS << StringRef(reinterpret_cast<char *>(&TheEntry), sizeof(Entry));
|
||||
}
|
||||
|
||||
// Create the string map entries.
|
||||
for (const OffloadingImage &Img : OffloadingData) {
|
||||
for (auto &KeyAndValue : Img.StringData) {
|
||||
StringEntry Map{StrTabOffset + StrTab.getOffset(KeyAndValue.first),
|
||||
StrTabOffset + StrTab.getOffset(KeyAndValue.second),
|
||||
KeyAndValue.second.size()};
|
||||
OS << StringRef(reinterpret_cast<char *>(&Map), sizeof(StringEntry));
|
||||
}
|
||||
}
|
||||
|
||||
StrTab.write(OS);
|
||||
// Add padding to required image alignment.
|
||||
OS.write_zeros(TheEntry.ImageOffset - OS.tell());
|
||||
OS << OffloadingData.Image->getBuffer();
|
||||
OS.write_zeros(BinaryDataSize - OS.tell());
|
||||
|
||||
for (const OffloadingImage &Img : OffloadingData)
|
||||
OS << Img.Image->getBuffer();
|
||||
|
||||
// Add final padding to required alignment.
|
||||
assert(TheHeader.Size >= OS.tell() && "Too much data written?");
|
||||
|
||||
@ -18,6 +18,7 @@ namespace llvm {
|
||||
namespace yaml {
|
||||
|
||||
bool yaml2offload(Binary &Doc, raw_ostream &Out, ErrorHandler EH) {
|
||||
SmallVector<object::OffloadBinary::OffloadingImage> Images;
|
||||
for (const auto &Member : Doc.Members) {
|
||||
object::OffloadBinary::OffloadingImage Image{};
|
||||
if (Member.ImageKind)
|
||||
@ -36,23 +37,24 @@ bool yaml2offload(Binary &Doc, raw_ostream &Out, ErrorHandler EH) {
|
||||
if (Member.Content)
|
||||
Member.Content->writeAsBinary(OS);
|
||||
Image.Image = MemoryBuffer::getMemBufferCopy(OS.str());
|
||||
|
||||
// Copy the data to a new buffer so we can modify the bytes directly.
|
||||
auto Buffer = object::OffloadBinary::write(Image);
|
||||
auto *TheHeader =
|
||||
reinterpret_cast<object::OffloadBinary::Header *>(&Buffer[0]);
|
||||
if (Doc.Version)
|
||||
TheHeader->Version = *Doc.Version;
|
||||
if (Doc.Size)
|
||||
TheHeader->Size = *Doc.Size;
|
||||
if (Doc.EntryOffset)
|
||||
TheHeader->EntryOffset = *Doc.EntryOffset;
|
||||
if (Doc.EntrySize)
|
||||
TheHeader->EntrySize = *Doc.EntrySize;
|
||||
|
||||
Out.write(Buffer.begin(), Buffer.size());
|
||||
Images.push_back(std::move(Image));
|
||||
}
|
||||
|
||||
// Copy the data to a new buffer so we can modify the bytes directly.
|
||||
auto Buffer = object::OffloadBinary::write(Images);
|
||||
auto *TheHeader =
|
||||
reinterpret_cast<object::OffloadBinary::Header *>(&Buffer[0]);
|
||||
if (Doc.Version)
|
||||
TheHeader->Version = *Doc.Version;
|
||||
if (Doc.Size)
|
||||
TheHeader->Size = *Doc.Size;
|
||||
if (Doc.EntriesOffset)
|
||||
TheHeader->EntriesOffset = *Doc.EntriesOffset;
|
||||
if (Doc.EntriesCount)
|
||||
TheHeader->EntriesCount = *Doc.EntriesCount;
|
||||
|
||||
Out.write(Buffer.begin(), Buffer.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ void ScalarEnumerationTraits<object::OffloadKind>::enumeration(
|
||||
ECase(OFK_OpenMP);
|
||||
ECase(OFK_Cuda);
|
||||
ECase(OFK_HIP);
|
||||
ECase(OFK_SYCL);
|
||||
ECase(OFK_LAST);
|
||||
#undef ECase
|
||||
IO.enumFallback<Hex16>(Value);
|
||||
@ -50,8 +51,8 @@ void MappingTraits<OffloadYAML::Binary>::mapping(IO &IO,
|
||||
IO.mapTag("!Offload", true);
|
||||
IO.mapOptional("Version", O.Version);
|
||||
IO.mapOptional("Size", O.Size);
|
||||
IO.mapOptional("EntryOffset", O.EntryOffset);
|
||||
IO.mapOptional("EntrySize", O.EntrySize);
|
||||
IO.mapOptional("EntriesOffset", O.EntriesOffset);
|
||||
IO.mapOptional("EntriesCount", O.EntriesCount);
|
||||
IO.mapRequired("Members", O.Members);
|
||||
IO.setContext(nullptr);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# RUN: yaml2obj %s | not obj2yaml 2>&1 | FileCheck %s
|
||||
!Offload
|
||||
EntrySize: 999999999
|
||||
EntriesCount: 999999999
|
||||
Members:
|
||||
- ImageKind: IMG_Cubin
|
||||
OffloadKind: OFK_OpenMP
|
||||
@ -1,6 +1,6 @@
|
||||
# RUN: yaml2obj %s | not obj2yaml 2>&1 | FileCheck %s
|
||||
!Offload
|
||||
EntryOffset: 999999999
|
||||
EntriesOffset: 999999999
|
||||
Members:
|
||||
- ImageKind: IMG_Cubin
|
||||
OffloadKind: OFK_OpenMP
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# RUN: yaml2obj %s | not obj2yaml 2>&1 | FileCheck %s
|
||||
!Offload
|
||||
Version: 2
|
||||
Version: 3
|
||||
Members:
|
||||
- ImageKind: IMG_Cubin
|
||||
OffloadKind: OFK_OpenMP
|
||||
|
||||
@ -18,7 +18,14 @@ Members:
|
||||
Value: "amdgcn-amd-amdhsa"
|
||||
- Key: "arch"
|
||||
Value: "gfx908"
|
||||
Content: "cafefeed"
|
||||
Content: "cafefeed"
|
||||
- ImageKind: IMG_Object
|
||||
OffloadKind: OFK_SYCL
|
||||
# OIF_Metadata
|
||||
Flags: 1
|
||||
String:
|
||||
- Key: "device"
|
||||
Value: "gpu"
|
||||
|
||||
# CHECK: --- !Offload
|
||||
# CHECK-NEXT: Members:
|
||||
@ -40,4 +47,10 @@ Members:
|
||||
# CHECK-NEXT: - Key: arch
|
||||
# CHECK-NEXT: Value: gfx908
|
||||
# CHECK-NEXT: Content: CAFEFEED
|
||||
# CHECK-NEXT: - ImageKind: IMG_Object
|
||||
# CHECK-NEXT: OffloadKind: OFK_SYCL
|
||||
# CHECK-NEXT: Flags: 1
|
||||
# CHECK-NEXT: String:
|
||||
# CHECK-NEXT: - Key: device
|
||||
# CHECK-NEXT: Value: gpu
|
||||
# CHECK-NEXT: ...
|
||||
|
||||
@ -16,49 +16,49 @@ using namespace llvm;
|
||||
|
||||
namespace {
|
||||
|
||||
void populateYAML(OffloadYAML::Binary &YAMLBinary, object::OffloadBinary &OB,
|
||||
void populateYAML(OffloadYAML::Binary &YAMLBinary,
|
||||
ArrayRef<std::unique_ptr<object::OffloadBinary>> OBinaries,
|
||||
UniqueStringSaver Saver) {
|
||||
YAMLBinary.Members.emplace_back();
|
||||
auto &Member = YAMLBinary.Members.back();
|
||||
Member.ImageKind = OB.getImageKind();
|
||||
Member.OffloadKind = OB.getOffloadKind();
|
||||
Member.Flags = OB.getFlags();
|
||||
if (!OB.strings().empty()) {
|
||||
Member.StringEntries = std::vector<OffloadYAML::Binary::StringEntry>();
|
||||
for (const auto &Entry : OB.strings())
|
||||
Member.StringEntries->emplace_back(OffloadYAML::Binary::StringEntry(
|
||||
{Saver.save(Entry.first), Saver.save(Entry.second)}));
|
||||
}
|
||||
for (const auto &OBinaryPtr : OBinaries) {
|
||||
object::OffloadBinary &OB = *OBinaryPtr;
|
||||
|
||||
if (!OB.getImage().empty())
|
||||
Member.Content = arrayRefFromStringRef(OB.getImage());
|
||||
YAMLBinary.Members.emplace_back();
|
||||
auto &Member = YAMLBinary.Members.back();
|
||||
Member.ImageKind = OB.getImageKind();
|
||||
Member.OffloadKind = OB.getOffloadKind();
|
||||
Member.Flags = OB.getFlags();
|
||||
if (!OB.strings().empty()) {
|
||||
Member.StringEntries = std::vector<OffloadYAML::Binary::StringEntry>();
|
||||
for (const auto &StringEntry : OB.strings())
|
||||
Member.StringEntries->emplace_back(OffloadYAML::Binary::StringEntry(
|
||||
{Saver.save(StringEntry.first), Saver.save(StringEntry.second)}));
|
||||
}
|
||||
|
||||
if (!OB.getImage().empty())
|
||||
Member.Content = arrayRefFromStringRef(OB.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
Expected<OffloadYAML::Binary *> dump(MemoryBufferRef Source,
|
||||
UniqueStringSaver Saver) {
|
||||
Expected<std::unique_ptr<object::OffloadBinary>> OB =
|
||||
object::OffloadBinary::create(Source);
|
||||
if (!OB)
|
||||
return OB.takeError();
|
||||
|
||||
std::unique_ptr<OffloadYAML::Binary> YAMLBinary =
|
||||
std::make_unique<OffloadYAML::Binary>();
|
||||
|
||||
YAMLBinary->Members = std::vector<OffloadYAML::Binary::Member>();
|
||||
|
||||
uint64_t Offset = 0;
|
||||
while (Offset < (*OB)->getMemoryBufferRef().getBufferSize()) {
|
||||
while (Offset < Source.getBufferSize()) {
|
||||
MemoryBufferRef Buffer = MemoryBufferRef(
|
||||
(*OB)->getData().drop_front(Offset), (*OB)->getFileName());
|
||||
auto BinaryOrErr = object::OffloadBinary::create(Buffer);
|
||||
if (!BinaryOrErr)
|
||||
return BinaryOrErr.takeError();
|
||||
Source.getBuffer().drop_front(Offset), Source.getBufferIdentifier());
|
||||
auto BinariesOrErr = object::OffloadBinary::create(Buffer);
|
||||
if (!BinariesOrErr)
|
||||
return BinariesOrErr.takeError();
|
||||
|
||||
object::OffloadBinary &Binary = **BinaryOrErr;
|
||||
SmallVector<std::unique_ptr<object::OffloadBinary>> &Binaries =
|
||||
*BinariesOrErr;
|
||||
populateYAML(*YAMLBinary, Binaries, Saver);
|
||||
|
||||
populateYAML(*YAMLBinary, Binary, Saver);
|
||||
|
||||
Offset += Binary.getSize();
|
||||
Offset += Binaries[0]->getSize();
|
||||
}
|
||||
|
||||
return YAMLBinary.release();
|
||||
|
||||
@ -50,7 +50,9 @@ TEST(OffloadingTest, checkOffloadingBinary) {
|
||||
FAIL();
|
||||
|
||||
// Make sure we get the same data out.
|
||||
auto &Binary = **BinaryOrErr;
|
||||
auto &Binaries = *BinaryOrErr;
|
||||
ASSERT_EQ(Binaries.size(), 1u);
|
||||
auto &Binary = *Binaries[0];
|
||||
ASSERT_EQ(Data.TheImageKind, Binary.getImageKind());
|
||||
ASSERT_EQ(Data.TheOffloadKind, Binary.getOffloadKind());
|
||||
ASSERT_EQ(Data.Flags, Binary.getFlags());
|
||||
@ -65,3 +67,209 @@ TEST(OffloadingTest, checkOffloadingBinary) {
|
||||
EXPECT_TRUE(Binary.getSize() % OffloadBinary::getAlignment() == 0);
|
||||
EXPECT_TRUE(Binary.getSize() == BinaryBuffer->getBuffer().size());
|
||||
}
|
||||
|
||||
static std::unique_ptr<MemoryBuffer>
|
||||
createMultiEntryBinary(size_t NumEntries,
|
||||
SmallVectorImpl<std::string> &StringStorage) {
|
||||
// Reserve space to prevent reallocation which would invalidate StringRefs.
|
||||
// Each entry needs: "id", id_value, "arch", arch_value, image_content = 5
|
||||
// strings.
|
||||
StringStorage.reserve(NumEntries * 5);
|
||||
|
||||
SmallVector<OffloadBinary::OffloadingImage> Images;
|
||||
|
||||
for (size_t i = 0; i < NumEntries; ++i) {
|
||||
OffloadBinary::OffloadingImage Data;
|
||||
Data.TheImageKind = static_cast<ImageKind>(i % IMG_LAST);
|
||||
Data.TheOffloadKind = static_cast<OffloadKind>(i % OFK_LAST);
|
||||
|
||||
MapVector<StringRef, StringRef> StringData;
|
||||
|
||||
StringStorage.push_back("id");
|
||||
StringStorage.push_back(std::to_string(i));
|
||||
StringData[StringStorage[StringStorage.size() - 2]] =
|
||||
StringStorage[StringStorage.size() - 1];
|
||||
|
||||
StringStorage.push_back("arch");
|
||||
StringStorage.push_back("gpu" + std::to_string(i));
|
||||
StringData[StringStorage[StringStorage.size() - 2]] =
|
||||
StringStorage[StringStorage.size() - 1];
|
||||
|
||||
Data.StringData = StringData;
|
||||
|
||||
// Make the last entry metadata-only (no image)
|
||||
if (i == NumEntries - 1) {
|
||||
Data.Flags = OIF_Metadata;
|
||||
Data.Image = MemoryBuffer::getMemBuffer("", "", false);
|
||||
} else {
|
||||
Data.Flags = i * 100;
|
||||
StringStorage.push_back("ImageData" + std::to_string(i));
|
||||
Data.Image = MemoryBuffer::getMemBuffer(StringStorage.back(), "", false);
|
||||
}
|
||||
|
||||
Images.push_back(std::move(Data));
|
||||
}
|
||||
|
||||
return MemoryBuffer::getMemBufferCopy(OffloadBinary::write(Images));
|
||||
}
|
||||
|
||||
// Test multi-entry binaries and extraction without index (get all entries).
|
||||
TEST(OffloadingTest, checkMultiEntryBinaryExtraction) {
|
||||
const size_t NumEntries = 5;
|
||||
SmallVector<std::string> StringStorage;
|
||||
auto BinaryBuffer = createMultiEntryBinary(NumEntries, StringStorage);
|
||||
|
||||
// Test extracting all entries (no index).
|
||||
auto BinariesOrErr = OffloadBinary::create(*BinaryBuffer);
|
||||
ASSERT_THAT_EXPECTED(BinariesOrErr, Succeeded());
|
||||
|
||||
auto &Binaries = *BinariesOrErr;
|
||||
ASSERT_EQ(Binaries.size(), NumEntries)
|
||||
<< "Expected all entries when no index provided";
|
||||
|
||||
// Verify each entry.
|
||||
for (size_t i = 0; i < NumEntries; ++i) {
|
||||
auto &Binary = *Binaries[i];
|
||||
EXPECT_EQ(Binary.getImageKind(), static_cast<ImageKind>(i % IMG_LAST));
|
||||
EXPECT_EQ(Binary.getOffloadKind(), static_cast<OffloadKind>(i % OFK_LAST));
|
||||
EXPECT_EQ(Binary.getIndex(), i);
|
||||
|
||||
std::string ExpectedId = std::to_string(i);
|
||||
std::string ExpectedArch = "gpu" + std::to_string(i);
|
||||
EXPECT_EQ(Binary.getString("id"), ExpectedId);
|
||||
EXPECT_EQ(Binary.getString("arch"), ExpectedArch);
|
||||
|
||||
// Last entry is metadata-only.
|
||||
if (i == NumEntries - 1) {
|
||||
EXPECT_EQ(Binary.getFlags(), OIF_Metadata);
|
||||
EXPECT_TRUE(Binary.getImage().empty());
|
||||
} else {
|
||||
EXPECT_EQ(Binary.getFlags(), i * 100);
|
||||
std::string ExpectedImage = "ImageData" + std::to_string(i);
|
||||
EXPECT_EQ(Binary.getImage(), ExpectedImage);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the size and alignment of the data is correct.
|
||||
EXPECT_TRUE(Binaries[0]->getSize() % OffloadBinary::getAlignment() == 0);
|
||||
EXPECT_TRUE(Binaries[0]->getSize() == BinaryBuffer->getBuffer().size());
|
||||
}
|
||||
|
||||
// Test index-based extraction from multi-entry binary.
|
||||
TEST(OffloadingTest, checkIndexBasedExtraction) {
|
||||
const size_t NumEntries = 5;
|
||||
SmallVector<std::string> StringStorage;
|
||||
auto BinaryBuffer = createMultiEntryBinary(NumEntries, StringStorage);
|
||||
|
||||
// Test extracting specific indices.
|
||||
for (uint64_t i = 0; i < NumEntries; ++i) {
|
||||
auto BinariesOrErr = OffloadBinary::create(*BinaryBuffer, i);
|
||||
ASSERT_THAT_EXPECTED(BinariesOrErr, Succeeded());
|
||||
|
||||
auto &Binaries = *BinariesOrErr;
|
||||
ASSERT_EQ(Binaries.size(), 1u) << "Expected single entry when using index";
|
||||
|
||||
auto &Binary = *Binaries[0];
|
||||
EXPECT_EQ(Binary.getImageKind(), static_cast<ImageKind>(i % IMG_LAST));
|
||||
EXPECT_EQ(Binary.getOffloadKind(), static_cast<OffloadKind>(i % OFK_LAST));
|
||||
EXPECT_EQ(Binary.getIndex(), i);
|
||||
|
||||
std::string ExpectedId = std::to_string(i);
|
||||
std::string ExpectedArch = "gpu" + std::to_string(i);
|
||||
EXPECT_EQ(Binary.getString("id"), ExpectedId);
|
||||
EXPECT_EQ(Binary.getString("arch"), ExpectedArch);
|
||||
|
||||
// Last entry is metadata-only.
|
||||
if (i == NumEntries - 1) {
|
||||
EXPECT_EQ(Binary.getFlags(), OIF_Metadata);
|
||||
EXPECT_TRUE(Binary.getImage().empty());
|
||||
} else {
|
||||
EXPECT_EQ(Binary.getFlags(), i * 100);
|
||||
std::string ExpectedImage = "ImageData" + std::to_string(i);
|
||||
EXPECT_EQ(Binary.getImage(), ExpectedImage);
|
||||
}
|
||||
}
|
||||
|
||||
// Test out-of-bounds index.
|
||||
auto OutOfBoundsOrErr = OffloadBinary::create(*BinaryBuffer, NumEntries + 10);
|
||||
EXPECT_THAT_EXPECTED(OutOfBoundsOrErr, Failed());
|
||||
}
|
||||
|
||||
TEST(OffloadingTest, checkEdgeCases) {
|
||||
// Test with empty string data.
|
||||
{
|
||||
OffloadBinary::OffloadingImage Data;
|
||||
Data.TheImageKind = IMG_Object;
|
||||
Data.TheOffloadKind = OFK_OpenMP;
|
||||
Data.Flags = 0;
|
||||
Data.StringData = MapVector<StringRef, StringRef>(); // Empty
|
||||
|
||||
std::string ImageContent = "TestImage";
|
||||
Data.Image = MemoryBuffer::getMemBuffer(ImageContent, "", false);
|
||||
|
||||
auto BinaryBuffer =
|
||||
MemoryBuffer::getMemBufferCopy(OffloadBinary::write(Data));
|
||||
auto BinariesOrErr = OffloadBinary::create(*BinaryBuffer);
|
||||
ASSERT_THAT_EXPECTED(BinariesOrErr, Succeeded());
|
||||
|
||||
auto &Binaries = *BinariesOrErr;
|
||||
ASSERT_EQ(Binaries.size(), 1u);
|
||||
EXPECT_TRUE(Binaries[0]->strings().empty());
|
||||
EXPECT_EQ(Binaries[0]->getImage(), ImageContent);
|
||||
}
|
||||
|
||||
// Test with empty image data.
|
||||
{
|
||||
std::string Key = "test";
|
||||
std::string Value = "value";
|
||||
|
||||
OffloadBinary::OffloadingImage Data;
|
||||
Data.TheImageKind = IMG_Object;
|
||||
Data.TheOffloadKind = OFK_SYCL;
|
||||
Data.Flags = 0;
|
||||
|
||||
MapVector<StringRef, StringRef> StringData;
|
||||
StringData[Key] = Value;
|
||||
Data.StringData = StringData;
|
||||
|
||||
Data.Image = MemoryBuffer::getMemBuffer("", "", false); // Empty image
|
||||
|
||||
auto BinaryBuffer =
|
||||
MemoryBuffer::getMemBufferCopy(OffloadBinary::write(Data));
|
||||
auto BinariesOrErr = OffloadBinary::create(*BinaryBuffer);
|
||||
ASSERT_THAT_EXPECTED(BinariesOrErr, Succeeded());
|
||||
|
||||
auto &Binaries = *BinariesOrErr;
|
||||
ASSERT_EQ(Binaries.size(), 1u);
|
||||
EXPECT_TRUE(Binaries[0]->getImage().empty());
|
||||
EXPECT_EQ(Binaries[0]->getString("test"), "value");
|
||||
}
|
||||
|
||||
// Test with large string values.
|
||||
{
|
||||
std::string Key = "large_key";
|
||||
std::string LargeValue(4096, 'X'); // Large value
|
||||
std::string ImageContent = "Image";
|
||||
|
||||
OffloadBinary::OffloadingImage Data;
|
||||
Data.TheImageKind = IMG_Bitcode;
|
||||
Data.TheOffloadKind = OFK_OpenMP;
|
||||
Data.Flags = 0;
|
||||
|
||||
MapVector<StringRef, StringRef> StringData;
|
||||
StringData[Key] = LargeValue;
|
||||
Data.StringData = StringData;
|
||||
|
||||
Data.Image = MemoryBuffer::getMemBuffer(ImageContent, "", false);
|
||||
|
||||
auto BinaryBuffer =
|
||||
MemoryBuffer::getMemBufferCopy(OffloadBinary::write(Data));
|
||||
auto BinariesOrErr = OffloadBinary::create(*BinaryBuffer);
|
||||
ASSERT_THAT_EXPECTED(BinariesOrErr, Succeeded());
|
||||
|
||||
auto &Binaries = *BinariesOrErr;
|
||||
ASSERT_EQ(Binaries.size(), 1u);
|
||||
EXPECT_EQ(Binaries[0]->getString("large_key"), LargeValue);
|
||||
EXPECT_EQ(Binaries[0]->getString("large_key").size(), 4096u);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user