The flag to enable decompressing offload bundles was not passed along to the OffloadBundleFatBin class. The --offloading option was not decompressing compressed objects.
603 lines
21 KiB
C++
603 lines
21 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/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");
|
|
|
|
// 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";
|
|
// Decompress this bundle first.
|
|
NextbundleStart = (*Buffer).getBuffer().find(Magic, Magic.size());
|
|
if (NextbundleStart == StringRef::npos)
|
|
NextbundleStart = (*Buffer).getBuffer().size();
|
|
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
|
|
MemoryBuffer::getMemBuffer(
|
|
(*Buffer).getBuffer().take_front(NextbundleStart), 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()) // TODO: add COFF Support.
|
|
return createStringError(object_error::parse_failed,
|
|
"COFF object files not supported");
|
|
|
|
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, int64_t Offset,
|
|
int64_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 Err;
|
|
|
|
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 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 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);
|
|
OS << MagicNumber;
|
|
OS.write(reinterpret_cast<const char *>(&Version), sizeof(Version));
|
|
OS.write(reinterpret_cast<const char *>(&CompressionMethod),
|
|
sizeof(CompressionMethod));
|
|
|
|
// Write size fields according to version.
|
|
if (Version == 2) {
|
|
uint32_t TotalFileSize32 = static_cast<uint32_t>(TotalFileSize64);
|
|
uint32_t UncompressedSize32 = static_cast<uint32_t>(UncompressedSize64);
|
|
OS.write(reinterpret_cast<const char *>(&TotalFileSize32),
|
|
sizeof(TotalFileSize32));
|
|
OS.write(reinterpret_cast<const char *>(&UncompressedSize32),
|
|
sizeof(UncompressedSize32));
|
|
} else { // Version 3.
|
|
OS.write(reinterpret_cast<const char *>(&TotalFileSize64),
|
|
sizeof(TotalFileSize64));
|
|
OS.write(reinterpret_cast<const char *>(&UncompressedSize64),
|
|
sizeof(UncompressedSize64));
|
|
}
|
|
|
|
OS.write(reinterpret_cast<const char *>(&TruncatedHash),
|
|
sizeof(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 {
|
|
uint32_t Magic;
|
|
uint16_t Version;
|
|
uint16_t Method;
|
|
};
|
|
|
|
struct V1Header {
|
|
CommonFields Common;
|
|
uint32_t UncompressedFileSize;
|
|
uint64_t Hash;
|
|
};
|
|
|
|
struct V2Header {
|
|
CommonFields Common;
|
|
uint32_t FileSize;
|
|
uint32_t UncompressedFileSize;
|
|
uint64_t Hash;
|
|
};
|
|
|
|
struct V3Header {
|
|
CommonFields Common;
|
|
uint64_t FileSize;
|
|
uint64_t UncompressedFileSize;
|
|
uint64_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);
|
|
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));
|
|
}
|