[clang] 4d27530 - [Offloading] Offload Binary Format V2: Support Multiple Entries (#169425)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 5 07:47:04 PST 2026
Author: Yury Plyakhin
Date: 2026-02-05T07:46:57-08:00
New Revision: 4d27530c69fdd692a69dc905f50b076d336da11a
URL: https://github.com/llvm/llvm-project/commit/4d27530c69fdd692a69dc905f50b076d336da11a
DIFF: https://github.com/llvm/llvm-project/commit/4d27530c69fdd692a69dc905f50b076d336da11a.diff
LOG: [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 at outlook.com>
Added:
llvm/test/ObjectYAML/Offload/malformed-entries-count.yaml
Modified:
clang/test/Driver/linker-wrapper-image.c
llvm/include/llvm/Object/OffloadBinary.h
llvm/include/llvm/ObjectYAML/OffloadYAML.h
llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
llvm/lib/Object/Binary.cpp
llvm/lib/Object/OffloadBinary.cpp
llvm/lib/ObjectYAML/OffloadEmitter.cpp
llvm/lib/ObjectYAML/OffloadYAML.cpp
llvm/test/ObjectYAML/Offload/malformed-offset.yaml
llvm/test/ObjectYAML/Offload/malformed-version.yaml
llvm/test/ObjectYAML/Offload/multiple_members.yaml
llvm/tools/obj2yaml/offload2yaml.cpp
llvm/unittests/Object/OffloadingTest.cpp
Removed:
llvm/test/ObjectYAML/Offload/malformed-entry-size.yaml
################################################################################
diff --git a/clang/test/Driver/linker-wrapper-image.c b/clang/test/Driver/linker-wrapper-image.c
index b9327121edcf9..2c0df8c6be925 100644
--- a/clang/test/Driver/linker-wrapper-image.c
+++ b/clang/test/Driver/linker-wrapper-image.c
@@ -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 }]
diff --git a/llvm/include/llvm/Object/OffloadBinary.h b/llvm/include/llvm/Object/OffloadBinary.h
index f3847c1624977..ff9c7d0d82dc2 100644
--- a/llvm/include/llvm/Object/OffloadBinary.h
+++ b/llvm/include/llvm/Object/OffloadBinary.h
@@ -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 @@ class OffloadBinary : public Binary {
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 @@ class OffloadBinary : public Binary {
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.
+ };
+
+ 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.
+ };
- /// Serialize the contents of \p File to a binary buffer to be read later.
- LLVM_ABI static SmallString<0> write(const OffloadingImage &);
+ 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 @@ class OffloadBinary : public Binary {
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 @@ class OffloadBinary : public Binary {
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
diff erent 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 @@ class OffloadBinary : public Binary {
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 @@ class OffloadFile : public OwningBinary<OffloadBinary> {
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.
diff --git a/llvm/include/llvm/ObjectYAML/OffloadYAML.h b/llvm/include/llvm/ObjectYAML/OffloadYAML.h
index f897b52aa8b0e..63ff561f3fcbf 100644
--- a/llvm/include/llvm/ObjectYAML/OffloadYAML.h
+++ b/llvm/include/llvm/ObjectYAML/OffloadYAML.h
@@ -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;
};
diff --git a/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp b/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
index 5f101cc6c946b..5e341ada1889e 100644
--- a/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
+++ b/llvm/lib/Frontend/Offloading/OffloadWrapper.cpp
@@ -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;
}
diff --git a/llvm/lib/Object/Binary.cpp b/llvm/lib/Object/Binary.cpp
index da2a7bb0a19da..30414257fa90b 100644
--- a/llvm/lib/Object/Binary.cpp
+++ b/llvm/lib/Object/Binary.cpp
@@ -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:
diff --git a/llvm/lib/Object/OffloadBinary.cpp b/llvm/lib/Object/OffloadBinary.cpp
index ef93bf1e9bfa7..011e9921daf08 100644
--- a/llvm/lib/Object/OffloadBinary.cpp
+++ b/llvm/lib/Object/OffloadBinary.cpp
@@ -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]);
+ return TheHeader;
+}
+
+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;
- if (TheEntry->ImageOffset > Buf.getBufferSize() ||
- TheEntry->StringOffset > Buf.getBufferSize() ||
- TheEntry->StringOffset + TheEntry->NumStrings * sizeof(StringEntry) >
- Buf.getBufferSize())
- return errorCodeToError(object_error::unexpected_eof);
+ 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 std::unique_ptr<OffloadBinary>(
- new OffloadBinary(Buf, TheHeader, TheEntry));
+ return Binaries;
}
-SmallString<0> OffloadBinary::write(const OffloadingImage &OffloadingData) {
+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?");
diff --git a/llvm/lib/ObjectYAML/OffloadEmitter.cpp b/llvm/lib/ObjectYAML/OffloadEmitter.cpp
index 131da68d77506..51167bd812df3 100644
--- a/llvm/lib/ObjectYAML/OffloadEmitter.cpp
+++ b/llvm/lib/ObjectYAML/OffloadEmitter.cpp
@@ -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;
}
diff --git a/llvm/lib/ObjectYAML/OffloadYAML.cpp b/llvm/lib/ObjectYAML/OffloadYAML.cpp
index d5a0edde2179f..c0e0ed41aaca9 100644
--- a/llvm/lib/ObjectYAML/OffloadYAML.cpp
+++ b/llvm/lib/ObjectYAML/OffloadYAML.cpp
@@ -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);
}
diff --git a/llvm/test/ObjectYAML/Offload/malformed-entry-size.yaml b/llvm/test/ObjectYAML/Offload/malformed-entries-count.yaml
similarity index 94%
rename from llvm/test/ObjectYAML/Offload/malformed-entry-size.yaml
rename to llvm/test/ObjectYAML/Offload/malformed-entries-count.yaml
index 3194607ae39a5..bb2a34277963f 100644
--- a/llvm/test/ObjectYAML/Offload/malformed-entry-size.yaml
+++ b/llvm/test/ObjectYAML/Offload/malformed-entries-count.yaml
@@ -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
diff --git a/llvm/test/ObjectYAML/Offload/malformed-offset.yaml b/llvm/test/ObjectYAML/Offload/malformed-offset.yaml
index 03c0431053cce..5aecfffd937bf 100644
--- a/llvm/test/ObjectYAML/Offload/malformed-offset.yaml
+++ b/llvm/test/ObjectYAML/Offload/malformed-offset.yaml
@@ -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
diff --git a/llvm/test/ObjectYAML/Offload/malformed-version.yaml b/llvm/test/ObjectYAML/Offload/malformed-version.yaml
index f9279a52e2764..99383491acce0 100644
--- a/llvm/test/ObjectYAML/Offload/malformed-version.yaml
+++ b/llvm/test/ObjectYAML/Offload/malformed-version.yaml
@@ -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
diff --git a/llvm/test/ObjectYAML/Offload/multiple_members.yaml b/llvm/test/ObjectYAML/Offload/multiple_members.yaml
index ac73d16e429a9..b07f7acd1f782 100644
--- a/llvm/test/ObjectYAML/Offload/multiple_members.yaml
+++ b/llvm/test/ObjectYAML/Offload/multiple_members.yaml
@@ -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: ...
diff --git a/llvm/tools/obj2yaml/offload2yaml.cpp b/llvm/tools/obj2yaml/offload2yaml.cpp
index 2b63e1278cd22..47e86f75514c0 100644
--- a/llvm/tools/obj2yaml/offload2yaml.cpp
+++ b/llvm/tools/obj2yaml/offload2yaml.cpp
@@ -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;
+
+ 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());
}
-
- 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();
-
- object::OffloadBinary &Binary = **BinaryOrErr;
+ Source.getBuffer().drop_front(Offset), Source.getBufferIdentifier());
+ auto BinariesOrErr = object::OffloadBinary::create(Buffer);
+ if (!BinariesOrErr)
+ return BinariesOrErr.takeError();
- populateYAML(*YAMLBinary, Binary, Saver);
+ SmallVector<std::unique_ptr<object::OffloadBinary>> &Binaries =
+ *BinariesOrErr;
+ populateYAML(*YAMLBinary, Binaries, Saver);
- Offset += Binary.getSize();
+ Offset += Binaries[0]->getSize();
}
return YAMLBinary.release();
diff --git a/llvm/unittests/Object/OffloadingTest.cpp b/llvm/unittests/Object/OffloadingTest.cpp
index 18c9efaceed06..b6ad6b69f25fc 100644
--- a/llvm/unittests/Object/OffloadingTest.cpp
+++ b/llvm/unittests/Object/OffloadingTest.cpp
@@ -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);
+ }
+}
More information about the cfe-commits
mailing list