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 <[email protected]> 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); + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
