Files
michaelselehov 274a34579f [OffloadBundler] Bound compressed bundles by header size, not magic scan (#206745)
When multiple offload bundles are concatenated, the unbundler
(clang-offload-bundler) and llvm-objdump --offloading located the end of
a compressed bundle, and the start of the next one, by scanning for the
next "CCOB" magic string starting right after the current header.

A zstd/zlib-compressed payload can legally contain those four bytes, so
the scan could stop in the middle of the compressed data and truncate
the bundle, corrupting the embedded code object. In practice this
produced a "decomposition" failure for hipBLASLt bf16 GEMMs on gfx942.

Use the authoritative total-size field recorded in the compressed bundle
header (format V2/V3) to compute the exact bundle boundary, and only
scan for the next magic past that point. Legacy bundles without a
recorded size (V1) keep the previous magic-scan fallback.

A skippable-frame fixture that embeds "CCOB" inside the compressed
payload is added to exercise the boundary logic from both
clang-offload-bundler and llvm-objdump --offloading.
2026-07-09 14:27:54 +02:00

641 lines
23 KiB
C++

//===- OffloadBundle.cpp - Utilities for offload bundles---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------===//
#include "llvm/Object/OffloadBundle.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/IR/Module.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/MC/StringTableBuilder.h"
#include "llvm/Object/Archive.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/COFF.h"
#include "llvm/Object/ELFObjectFile.h"
#include "llvm/Object/Error.h"
#include "llvm/Object/IRObjectFile.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/Timer.h"
using namespace llvm;
using namespace llvm::object;
static TimerGroup OffloadBundlerTimerGroup("Offload Bundler Timer Group",
"Timer group for offload bundler");
// Returns the on-disk size recorded in the compressed offload bundle header at
// the start of \p Blob, or std::nullopt if the header carries no size field.
static std::optional<size_t> getCompressedBundleSize(StringRef Blob) {
Expected<CompressedOffloadBundle::CompressedBundleHeader> HeaderOrErr =
CompressedOffloadBundle::CompressedBundleHeader::tryParse(Blob);
if (!HeaderOrErr) {
consumeError(HeaderOrErr.takeError());
return std::nullopt;
}
return HeaderOrErr->FileSize;
}
// Extract an Offload bundle (usually a Offload Bundle) from a fat_bin
// section.
Error extractOffloadBundle(MemoryBufferRef Contents, uint64_t SectionOffset,
StringRef FileName,
SmallVectorImpl<OffloadBundleFatBin> &Bundles) {
size_t Offset = 0;
size_t NextbundleStart = 0;
StringRef Magic;
std::unique_ptr<MemoryBuffer> Buffer;
// There could be multiple offloading bundles stored at this section.
while ((NextbundleStart != StringRef::npos) &&
(Offset < Contents.getBuffer().size())) {
Buffer =
MemoryBuffer::getMemBuffer(Contents.getBuffer().drop_front(Offset), "",
/*RequiresNullTerminator=*/false);
if (identify_magic((*Buffer).getBuffer()) ==
file_magic::offload_bundle_compressed) {
Magic = "CCOB";
// Locate this bundle's end and the next bundle from the header size.
size_t CurBundleEnd;
if (std::optional<size_t> Size =
getCompressedBundleSize((*Buffer).getBuffer())) {
CurBundleEnd = *Size;
NextbundleStart = (*Buffer).getBuffer().find(Magic, *Size);
} else {
// Legacy bundle without a recorded size: fall back to magic scanning.
NextbundleStart = (*Buffer).getBuffer().find(Magic, Magic.size());
CurBundleEnd = NextbundleStart;
}
if (NextbundleStart == StringRef::npos) {
NextbundleStart = (*Buffer).getBuffer().size();
if (CurBundleEnd == StringRef::npos)
CurBundleEnd = (*Buffer).getBuffer().size();
}
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
MemoryBuffer::getMemBuffer(
(*Buffer).getBuffer().take_front(CurBundleEnd), FileName, false);
if (std::error_code EC = CodeOrErr.getError())
return createFileError(FileName, EC);
Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
CompressedOffloadBundle::decompress(**CodeOrErr, nullptr);
if (!DecompressedBufferOrErr)
return createStringError("failed to decompress input: " +
toString(DecompressedBufferOrErr.takeError()));
auto FatBundleOrErr = OffloadBundleFatBin::create(
**DecompressedBufferOrErr, Offset, FileName, true);
if (!FatBundleOrErr)
return FatBundleOrErr.takeError();
// Add current Bundle to list.
Bundles.emplace_back(std::move(**FatBundleOrErr));
} else if (identify_magic((*Buffer).getBuffer()) ==
file_magic::offload_bundle) {
// Create the OffloadBundleFatBin object. This will also create the Bundle
// Entry list info.
auto FatBundleOrErr = OffloadBundleFatBin::create(
*Buffer, SectionOffset + Offset, FileName);
if (!FatBundleOrErr)
return FatBundleOrErr.takeError();
// Add current Bundle to list.
Bundles.emplace_back(std::move(**FatBundleOrErr));
Magic = "__CLANG_OFFLOAD_BUNDLE__";
NextbundleStart = (*Buffer).getBuffer().find(Magic, Magic.size());
}
if (NextbundleStart != StringRef::npos)
Offset += NextbundleStart;
}
return Error::success();
}
Error OffloadBundleFatBin::readEntries(StringRef Buffer,
uint64_t SectionOffset) {
uint64_t NumOfEntries = 0;
BinaryStreamReader Reader(Buffer, llvm::endianness::little);
// Read the Magic String first.
StringRef Magic;
if (auto EC = Reader.readFixedString(Magic, 24))
return errorCodeToError(object_error::parse_failed);
// Read the number of Code Objects (Entries) in the current Bundle.
if (auto EC = Reader.readInteger(NumOfEntries))
return errorCodeToError(object_error::parse_failed);
NumberOfEntries = NumOfEntries;
// For each Bundle Entry (code object).
for (uint64_t I = 0; I < NumOfEntries; I++) {
uint64_t EntrySize;
uint64_t EntryOffset;
uint64_t EntryIDSize;
StringRef EntryID;
if (Error Err = Reader.readInteger(EntryOffset))
return Err;
if (Error Err = Reader.readInteger(EntrySize))
return Err;
if (Error Err = Reader.readInteger(EntryIDSize))
return Err;
if (Error Err = Reader.readFixedString(EntryID, EntryIDSize))
return Err;
auto Entry = std::make_unique<OffloadBundleEntry>(
EntryOffset + SectionOffset, EntrySize, EntryIDSize, EntryID);
Entries.push_back(*Entry);
}
return Error::success();
}
Expected<std::unique_ptr<OffloadBundleFatBin>>
OffloadBundleFatBin::create(MemoryBufferRef Buf, uint64_t SectionOffset,
StringRef FileName, bool Decompress) {
if (Buf.getBufferSize() < 24)
return errorCodeToError(object_error::parse_failed);
// Check for magic bytes.
if ((identify_magic(Buf.getBuffer()) != file_magic::offload_bundle) &&
(identify_magic(Buf.getBuffer()) !=
file_magic::offload_bundle_compressed))
return errorCodeToError(object_error::parse_failed);
std::unique_ptr<OffloadBundleFatBin> TheBundle(
new OffloadBundleFatBin(Buf, FileName, Decompress));
// Read the Bundle Entries.
Error Err =
TheBundle->readEntries(Buf.getBuffer(), Decompress ? 0 : SectionOffset);
if (Err)
return Err;
return std::move(TheBundle);
}
Error OffloadBundleFatBin::extractBundle(const ObjectFile &Source) {
// This will extract all entries in the Bundle.
for (OffloadBundleEntry &Entry : Entries) {
if (Entry.Size == 0)
continue;
// create output file name. Which should be
// <fileName>-offset<Offset>-size<Size>.co"
std::string Str = getFileName().str() + "-offset" + itostr(Entry.Offset) +
"-size" + itostr(Entry.Size) + ".co";
if (Error Err = object::extractCodeObject(Source, Entry.Offset, Entry.Size,
StringRef(Str)))
return Err;
}
return Error::success();
}
Error object::extractOffloadBundleFatBinary(
const ObjectFile &Obj, SmallVectorImpl<OffloadBundleFatBin> &Bundles) {
// Ignore unsupported object formats.
if (!Obj.isELF() && !Obj.isCOFF())
return Error::success();
// Iterate through Sections until we find an offload_bundle section.
for (SectionRef Sec : Obj.sections()) {
Expected<StringRef> Buffer = Sec.getContents();
if (!Buffer)
return Buffer.takeError();
// If it does not start with the reserved suffix, just skip this section.
if ((llvm::identify_magic(*Buffer) == file_magic::offload_bundle) ||
(llvm::identify_magic(*Buffer) ==
file_magic::offload_bundle_compressed)) {
uint64_t SectionOffset = 0;
if (Obj.isELF()) {
SectionOffset = ELFSectionRef(Sec).getOffset();
} else if (Obj.isCOFF()) {
const COFFObjectFile &COFF = cast<COFFObjectFile>(Obj);
Expected<const coff_section *> SecOrErr =
COFF.getSection(COFF.getSectionID(Sec));
if (!SecOrErr)
return SecOrErr.takeError();
SectionOffset = (*SecOrErr)->PointerToRawData;
}
MemoryBufferRef Contents(*Buffer, Obj.getFileName());
if (Error Err = extractOffloadBundle(Contents, SectionOffset,
Obj.getFileName(), Bundles))
return Err;
}
}
return Error::success();
}
Error object::extractCodeObject(const ObjectFile &Source, size_t Offset,
size_t Size, StringRef OutputFileName) {
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(OutputFileName, Size);
if (!BufferOrErr)
return BufferOrErr.takeError();
Expected<MemoryBufferRef> InputBuffOrErr = Source.getMemoryBufferRef();
if (Error Err = InputBuffOrErr.takeError())
return createFileError(Source.getFileName(), std::move(Err));
if (Size > InputBuffOrErr->getBufferSize())
return createStringError("size in URI (%zu) is larger than source (%zu)",
Size, InputBuffOrErr->getBufferSize());
if (Offset > InputBuffOrErr->getBufferSize())
return createStringError(
"offset in URI (%zu) is beyond the end of the source (%zu)", Offset,
InputBuffOrErr->getBufferSize());
if (Offset + Size > InputBuffOrErr->getBufferSize())
return createStringError(
"offset + size (%zu) in URI is beyond the end of the source (%zu)",
Offset + Size, InputBuffOrErr->getBufferSize());
std::unique_ptr<FileOutputBuffer> Buf = std::move(*BufferOrErr);
std::copy(InputBuffOrErr->getBufferStart() + Offset,
InputBuffOrErr->getBufferStart() + Offset + Size,
Buf->getBufferStart());
if (Error E = Buf->commit())
return createFileError(OutputFileName, std::move(E));
return Error::success();
}
Error object::extractCodeObject(const MemoryBufferRef Buffer, int64_t Offset,
int64_t Size, StringRef OutputFileName) {
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
FileOutputBuffer::create(OutputFileName, Size);
if (!BufferOrErr)
return BufferOrErr.takeError();
std::unique_ptr<FileOutputBuffer> Buf = std::move(*BufferOrErr);
std::copy(Buffer.getBufferStart() + Offset,
Buffer.getBufferStart() + Offset + Size, Buf->getBufferStart());
return Buf->commit();
}
// given a file name, offset, and size, extract data into a code object file,
// into file "<SourceFile>-offset<Offset>-size<Size>.co".
Error object::extractOffloadBundleByURI(StringRef URIstr) {
// create a URI object
Expected<std::unique_ptr<OffloadBundleURI>> UriOrErr(
OffloadBundleURI::createOffloadBundleURI(URIstr, FILE_URI));
if (!UriOrErr)
return UriOrErr.takeError();
OffloadBundleURI &Uri = **UriOrErr;
std::string OutputFile = Uri.FileName.str();
OutputFile +=
"-offset" + itostr(Uri.Offset) + "-size" + itostr(Uri.Size) + ".co";
// Create an ObjectFile object from uri.file_uri.
auto ObjOrErr = ObjectFile::createObjectFile(Uri.FileName);
if (!ObjOrErr)
return ObjOrErr.takeError();
auto Obj = ObjOrErr->getBinary();
if (Error Err =
object::extractCodeObject(*Obj, Uri.Offset, Uri.Size, OutputFile))
return createFileError(Uri.FileName, std::move(Err));
return Error::success();
}
// Utility function to format numbers with commas.
static std::string formatWithCommas(unsigned long long Value) {
std::string Num = std::to_string(Value);
int InsertPosition = Num.length() - 3;
while (InsertPosition > 0) {
Num.insert(InsertPosition, ",");
InsertPosition -= 3;
}
return Num;
}
Expected<std::unique_ptr<MemoryBuffer>>
CompressedOffloadBundle::compress(compression::Params P,
const MemoryBuffer &Input, uint16_t Version,
raw_ostream *VerboseStream) {
if (!compression::zstd::isAvailable() && !compression::zlib::isAvailable())
return createStringError("compression not supported.");
Timer HashTimer("Hash Calculation Timer", "Hash calculation time",
OffloadBundlerTimerGroup);
if (VerboseStream)
HashTimer.startTimer();
MD5 Hash;
MD5::MD5Result Result;
Hash.update(Input.getBuffer());
Hash.final(Result);
uint64_t TruncatedHash = Result.low();
if (VerboseStream)
HashTimer.stopTimer();
SmallVector<uint8_t, 0> CompressedBuffer;
auto BufferUint8 = ArrayRef<uint8_t>(
reinterpret_cast<const uint8_t *>(Input.getBuffer().data()),
Input.getBuffer().size());
Timer CompressTimer("Compression Timer", "Compression time",
OffloadBundlerTimerGroup);
if (VerboseStream)
CompressTimer.startTimer();
compression::compress(P, BufferUint8, CompressedBuffer);
if (VerboseStream)
CompressTimer.stopTimer();
uint16_t CompressionMethod = static_cast<uint16_t>(P.format);
// Store sizes in 64-bit variables first.
uint64_t UncompressedSize64 = Input.getBuffer().size();
uint64_t TotalFileSize64;
// Calculate total file size based on version.
if (Version == 2) {
// For V2, ensure the sizes don't exceed 32-bit limit.
if (UncompressedSize64 > std::numeric_limits<uint32_t>::max())
return createStringError("uncompressed size (%llu) exceeds version 2 "
"unsigned 32-bit integer limit",
UncompressedSize64);
TotalFileSize64 = MagicNumber.size() + sizeof(uint32_t) + sizeof(Version) +
sizeof(CompressionMethod) + sizeof(uint32_t) +
sizeof(TruncatedHash) + CompressedBuffer.size();
if (TotalFileSize64 > std::numeric_limits<uint32_t>::max())
return createStringError("total file size (%llu) exceeds version 2 "
"unsigned 32-bit integer limit",
TotalFileSize64);
} else { // Version 3.
TotalFileSize64 = MagicNumber.size() + sizeof(uint64_t) + sizeof(Version) +
sizeof(CompressionMethod) + sizeof(uint64_t) +
sizeof(TruncatedHash) + CompressedBuffer.size();
}
SmallVector<char, 0> FinalBuffer;
raw_svector_ostream OS(FinalBuffer);
// The on-disk header is always little-endian, independent of the host.
support::endian::Writer Writer(OS, endianness::little);
OS << MagicNumber;
Writer.write(Version);
Writer.write(CompressionMethod);
// Write size fields according to version.
if (Version == 2) {
Writer.write(static_cast<uint32_t>(TotalFileSize64));
Writer.write(static_cast<uint32_t>(UncompressedSize64));
} else { // Version 3.
Writer.write(TotalFileSize64);
Writer.write(UncompressedSize64);
}
Writer.write(TruncatedHash);
OS.write(reinterpret_cast<const char *>(CompressedBuffer.data()),
CompressedBuffer.size());
if (VerboseStream) {
auto MethodUsed = P.format == compression::Format::Zstd ? "zstd" : "zlib";
double CompressionRate =
static_cast<double>(UncompressedSize64) / CompressedBuffer.size();
double CompressionTimeSeconds = CompressTimer.getTotalTime().getWallTime();
double CompressionSpeedMBs =
(UncompressedSize64 / (1024.0 * 1024.0)) / CompressionTimeSeconds;
*VerboseStream << "Compressed bundle format version: " << Version << "\n"
<< "Total file size (including headers): "
<< formatWithCommas(TotalFileSize64) << " bytes\n"
<< "Compression method used: " << MethodUsed << "\n"
<< "Compression level: " << P.level << "\n"
<< "Binary size before compression: "
<< formatWithCommas(UncompressedSize64) << " bytes\n"
<< "Binary size after compression: "
<< formatWithCommas(CompressedBuffer.size()) << " bytes\n"
<< "Compression rate: " << format("%.2lf", CompressionRate)
<< "\n"
<< "Compression ratio: "
<< format("%.2lf%%", 100.0 / CompressionRate) << "\n"
<< "Compression speed: "
<< format("%.2lf MB/s", CompressionSpeedMBs) << "\n"
<< "Truncated MD5 hash: " << format_hex(TruncatedHash, 16)
<< "\n";
}
return MemoryBuffer::getMemBufferCopy(
StringRef(FinalBuffer.data(), FinalBuffer.size()));
}
// Use packed structs to avoid padding, such that the structs map the serialized
// format.
LLVM_PACKED_START
union RawCompressedBundleHeader {
struct CommonFields {
support::ulittle32_t Magic;
support::ulittle16_t Version;
support::ulittle16_t Method;
};
struct V1Header {
CommonFields Common;
support::ulittle32_t UncompressedFileSize;
support::ulittle64_t Hash;
};
struct V2Header {
CommonFields Common;
support::ulittle32_t FileSize;
support::ulittle32_t UncompressedFileSize;
support::ulittle64_t Hash;
};
struct V3Header {
CommonFields Common;
support::ulittle64_t FileSize;
support::ulittle64_t UncompressedFileSize;
support::ulittle64_t Hash;
};
CommonFields Common;
V1Header V1;
V2Header V2;
V3Header V3;
};
LLVM_PACKED_END
// Helper method to get header size based on version.
static size_t getHeaderSize(uint16_t Version) {
switch (Version) {
case 1:
return sizeof(RawCompressedBundleHeader::V1Header);
case 2:
return sizeof(RawCompressedBundleHeader::V2Header);
case 3:
return sizeof(RawCompressedBundleHeader::V3Header);
default:
llvm_unreachable("Unsupported version");
}
}
Expected<CompressedOffloadBundle::CompressedBundleHeader>
CompressedOffloadBundle::CompressedBundleHeader::tryParse(StringRef Blob) {
assert(Blob.size() >= sizeof(RawCompressedBundleHeader::CommonFields));
assert(identify_magic(Blob) == file_magic::offload_bundle_compressed);
RawCompressedBundleHeader Header;
std::memcpy(&Header, Blob.data(), std::min(Blob.size(), sizeof(Header)));
CompressedBundleHeader Normalized;
Normalized.Version = Header.Common.Version;
size_t RequiredSize = getHeaderSize(Normalized.Version);
if (Blob.size() < RequiredSize)
return createStringError("compressed bundle header size too small");
switch (Normalized.Version) {
case 1:
Normalized.UncompressedFileSize = Header.V1.UncompressedFileSize;
Normalized.Hash = Header.V1.Hash;
break;
case 2:
Normalized.FileSize = Header.V2.FileSize;
Normalized.UncompressedFileSize = Header.V2.UncompressedFileSize;
Normalized.Hash = Header.V2.Hash;
break;
case 3:
Normalized.FileSize = Header.V3.FileSize;
Normalized.UncompressedFileSize = Header.V3.UncompressedFileSize;
Normalized.Hash = Header.V3.Hash;
break;
default:
return createStringError("unknown compressed bundle version");
}
// Determine compression format.
switch (Header.Common.Method) {
case static_cast<uint16_t>(compression::Format::Zlib):
case static_cast<uint16_t>(compression::Format::Zstd):
Normalized.CompressionFormat =
static_cast<compression::Format>(Header.Common.Method.value());
break;
default:
return createStringError("unknown compressing method");
}
return Normalized;
}
Expected<std::unique_ptr<MemoryBuffer>>
CompressedOffloadBundle::decompress(const MemoryBuffer &Input,
raw_ostream *VerboseStream) {
StringRef Blob = Input.getBuffer();
// Check minimum header size (using V1 as it's the smallest).
if (Blob.size() < sizeof(RawCompressedBundleHeader::CommonFields))
return MemoryBuffer::getMemBufferCopy(Blob);
if (identify_magic(Blob) != file_magic::offload_bundle_compressed) {
if (VerboseStream)
*VerboseStream << "Uncompressed bundle\n";
return MemoryBuffer::getMemBufferCopy(Blob);
}
Expected<CompressedBundleHeader> HeaderOrErr =
CompressedBundleHeader::tryParse(Blob);
if (!HeaderOrErr)
return HeaderOrErr.takeError();
const CompressedBundleHeader &Normalized = *HeaderOrErr;
unsigned ThisVersion = Normalized.Version;
size_t HeaderSize = getHeaderSize(ThisVersion);
compression::Format CompressionFormat = Normalized.CompressionFormat;
size_t TotalFileSize = Normalized.FileSize.value_or(0);
size_t UncompressedSize = Normalized.UncompressedFileSize;
auto StoredHash = Normalized.Hash;
Timer DecompressTimer("Decompression Timer", "Decompression time",
OffloadBundlerTimerGroup);
if (VerboseStream)
DecompressTimer.startTimer();
SmallVector<uint8_t, 0> DecompressedData;
StringRef CompressedData =
Blob.substr(HeaderSize, TotalFileSize - HeaderSize);
if (Error DecompressionError = compression::decompress(
CompressionFormat, arrayRefFromStringRef(CompressedData),
DecompressedData, UncompressedSize))
return createStringError("could not decompress embedded file contents: " +
toString(std::move(DecompressionError)));
if (VerboseStream) {
DecompressTimer.stopTimer();
double DecompressionTimeSeconds =
DecompressTimer.getTotalTime().getWallTime();
// Recalculate MD5 hash for integrity check.
Timer HashRecalcTimer("Hash Recalculation Timer", "Hash recalculation time",
OffloadBundlerTimerGroup);
HashRecalcTimer.startTimer();
MD5 Hash;
MD5::MD5Result Result;
Hash.update(ArrayRef<uint8_t>(DecompressedData));
Hash.final(Result);
uint64_t RecalculatedHash = Result.low();
HashRecalcTimer.stopTimer();
bool HashMatch = (StoredHash == RecalculatedHash);
double CompressionRate =
static_cast<double>(UncompressedSize) / CompressedData.size();
double DecompressionSpeedMBs =
(UncompressedSize / (1024.0 * 1024.0)) / DecompressionTimeSeconds;
*VerboseStream << "Compressed bundle format version: " << ThisVersion
<< "\n";
if (ThisVersion >= 2)
*VerboseStream << "Total file size (from header): "
<< formatWithCommas(TotalFileSize) << " bytes\n";
*VerboseStream
<< "Decompression method: "
<< (CompressionFormat == compression::Format::Zlib ? "zlib" : "zstd")
<< "\n"
<< "Size before decompression: "
<< formatWithCommas(CompressedData.size()) << " bytes\n"
<< "Size after decompression: " << formatWithCommas(UncompressedSize)
<< " bytes\n"
<< "Compression rate: " << format("%.2lf", CompressionRate) << "\n"
<< "Compression ratio: " << format("%.2lf%%", 100.0 / CompressionRate)
<< "\n"
<< "Decompression speed: "
<< format("%.2lf MB/s", DecompressionSpeedMBs) << "\n"
<< "Stored hash: " << format_hex(StoredHash, 16) << "\n"
<< "Recalculated hash: " << format_hex(RecalculatedHash, 16) << "\n"
<< "Hashes match: " << (HashMatch ? "Yes" : "No") << "\n";
}
return MemoryBuffer::getMemBufferCopy(toStringRef(DecompressedData));
}