[clang] 5b93aeb - clang-offload-bundler incorrectly errors on multi-CCOB binaries (#182579)
via cfe-commits
cfe-commits at lists.llvm.org
Mon May 25 11:13:17 PDT 2026
Author: David Salinas
Date: 2026-05-25T14:13:12-04:00
New Revision: 5b93aeb21818cfdb142b14e70ef4eb0363ca9d88
URL: https://github.com/llvm/llvm-project/commit/5b93aeb21818cfdb142b14e70ef4eb0363ca9d88
DIFF: https://github.com/llvm/llvm-project/commit/5b93aeb21818cfdb142b14e70ef4eb0363ca9d88.diff
LOG: clang-offload-bundler incorrectly errors on multi-CCOB binaries (#182579)
Issue: https://github.com/ROCm/llvm-project/issues/448
Objects can have multiple Clang Compressed Offload Bundles (CCOB) in the
.hip_fatbin section. This happens when there are multiple
translation/compilation units built and then linked together into an
Archive or Shared Object. The resulting .hip_fatbin section will have
multiple offload bundles delimited by the magic string "CCOB" (on a 4k
alignment boundary). The Clang Offload bundler API, when a List of
bundle entries is requested, was not properly iterating (looping) over
each separate bundle.
REPRODUCTION
Test File: librocblas.so.5 from ROCm 6.x distribution
.hip_fatbin section: 8,163,887 bytes containing 64 concatenated CCOBs
Extract the .hip_fatbin section with:
objcopy --dump-section .hip_fatbin=fatbin.bin binary
Structure:
Offset 0x0: CCOB header + 1.16 MB compressed (→ 12.41 MB uncompressed)
Offset 0x129000: CCOB header + 1.01 MB compressed (→ 13.14 MB
uncompressed)
Offset 0x227000: CCOB header + 36.5 KB compressed (→ 1.21 MB
uncompressed)
... (61 more bundles)
Command:
$ clang-offload-bundler --type=o --input=librocblas.so.5 --list
Error:
clang-offload-bundler: error: Failed to decompress input: Could not
decompress embedded file contents: Src size is incorrect
Expected:
Should list all target triples in the bundle, or at minimum process
the first bundle without error.
Added:
clang/test/Driver/clang-offload-bundler-multi-compress.c
Modified:
clang/lib/Driver/OffloadBundler.cpp
Removed:
################################################################################
diff --git a/clang/lib/Driver/OffloadBundler.cpp b/clang/lib/Driver/OffloadBundler.cpp
index 8dd57c5c3b4a5..9d6f32c4a4e8f 100644
--- a/clang/lib/Driver/OffloadBundler.cpp
+++ b/clang/lib/Driver/OffloadBundler.cpp
@@ -192,13 +192,13 @@ class FileHandler {
/// Update the file handler with information from the header of the bundled
/// file.
- virtual Error ReadHeader(MemoryBuffer &Input) = 0;
+ virtual Error ReadHeader(StringRef FC) = 0;
/// Read the marker of the next bundled to be read in the file. The bundle
/// name is returned if there is one in the file, or `std::nullopt` if there
/// are no more bundles to be read.
virtual Expected<std::optional<StringRef>>
- ReadBundleStart(MemoryBuffer &Input) = 0;
+ ReadBundleStart(StringRef Input) = 0;
/// Read the marker that closes the current bundle.
virtual Error ReadBundleEnd(MemoryBuffer &Input) = 0;
@@ -227,33 +227,54 @@ class FileHandler {
/// List bundle IDs in \a Input.
virtual Error listBundleIDs(MemoryBuffer &Input) {
- if (Error Err = ReadHeader(Input))
- return Err;
- return forEachBundle(Input, [&](const BundleInfo &Info) -> Error {
- llvm::outs() << Info.BundleID << '\n';
- Error Err = listBundleIDsCallback(Input, Info);
+ size_t NextBundleStart = 0;
+ StringRef BufferString = Input.getBuffer();
+ while (NextBundleStart != StringRef::npos) {
+
+ // Drop the data that has already been processed/read.
+ BufferString = BufferString.drop_front(NextBundleStart);
+
+ // Read the header.
+ Error Err = ReadHeader(BufferString);
if (Err)
return Err;
- return Error::success();
- });
+
+ Err = forEachBundle(BufferString, [&](const BundleInfo &Info) -> Error {
+ llvm::outs() << Info.BundleID << '\n';
+ Error Err = listBundleIDsCallback(Input, Info);
+ if (Err)
+ return Err;
+ return Error::success();
+ });
+
+ if (Err)
+ return Err;
+
+ // Find the beginning of the next Bundle, if it exists.
+ NextBundleStart = BufferString.find(StringRef(OFFLOAD_BUNDLER_MAGIC_STR),
+ sizeof(OFFLOAD_BUNDLER_MAGIC_STR));
+ }
+ return Error::success();
}
/// Get bundle IDs in \a Input in \a BundleIds.
virtual Error getBundleIDs(MemoryBuffer &Input,
std::set<StringRef> &BundleIds) {
- if (Error Err = ReadHeader(Input))
+
+ if (Error Err = ReadHeader(Input.getBuffer()))
return Err;
- return forEachBundle(Input, [&](const BundleInfo &Info) -> Error {
- BundleIds.insert(Info.BundleID);
- Error Err = listBundleIDsCallback(Input, Info);
- if (Err)
- return Err;
- return Error::success();
- });
+ return forEachBundle(Input.getBuffer(),
+ [&](const BundleInfo &Info) -> Error {
+ BundleIds.insert(Info.BundleID);
+ Error Err = listBundleIDsCallback(Input, Info);
+ if (Err)
+ return Err;
+ return Error::success();
+ });
}
/// For each bundle in \a Input, do \a Func.
- Error forEachBundle(MemoryBuffer &Input,
+ Error forEachBundle(StringRef Input,
std::function<Error(const BundleInfo &)> Func) {
while (true) {
Expected<std::optional<StringRef>> CurTripleOrErr =
@@ -347,9 +368,7 @@ class BinaryFileHandler final : public FileHandler {
~BinaryFileHandler() final {}
- Error ReadHeader(MemoryBuffer &Input) final {
- StringRef FC = Input.getBuffer();
-
+ Error ReadHeader(StringRef FC) final {
// Initialize the current bundle with the end of the container.
CurBundleInfo = BundlesInfo.end();
@@ -404,7 +423,6 @@ class BinaryFileHandler final : public FileHandler {
if (!Offset || Offset + Size > FC.size())
return Error::success();
- assert(!BundlesInfo.contains(Triple) && "Triple is duplicated??");
BundlesInfo[Triple] = BinaryBundleInfo(Size, Offset);
}
// Set the iterator to where we will start to read.
@@ -413,8 +431,7 @@ class BinaryFileHandler final : public FileHandler {
return Error::success();
}
- Expected<std::optional<StringRef>>
- ReadBundleStart(MemoryBuffer &Input) final {
+ Expected<std::optional<StringRef>> ReadBundleStart(StringRef Input) final {
if (NextBundleInfo == BundlesInfo.end())
return std::nullopt;
CurBundleInfo = NextBundleInfo++;
@@ -578,10 +595,9 @@ class ObjectFileHandler final : public FileHandler {
~ObjectFileHandler() final {}
- Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); }
+ Error ReadHeader(StringRef Input) final { return Error::success(); }
- Expected<std::optional<StringRef>>
- ReadBundleStart(MemoryBuffer &Input) final {
+ Expected<std::optional<StringRef>> ReadBundleStart(StringRef Input) final {
while (NextSection != Obj->section_end()) {
CurrentSection = NextSection;
++NextSection;
@@ -789,11 +805,9 @@ class TextFileHandler final : public FileHandler {
size_t ReadChars = 0u;
protected:
- Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); }
+ Error ReadHeader(StringRef Input) final { return Error::success(); }
- Expected<std::optional<StringRef>>
- ReadBundleStart(MemoryBuffer &Input) final {
- StringRef FC = Input.getBuffer();
+ Expected<std::optional<StringRef>> ReadBundleStart(StringRef FC) final {
// Find start of the bundle.
ReadChars = FC.find(BundleStartString, ReadChars);
@@ -1267,7 +1281,8 @@ CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input,
DecompressTimer.startTimer();
SmallVector<uint8_t, 0> DecompressedData;
- StringRef CompressedData = Blob.substr(HeaderSize);
+ StringRef CompressedData =
+ Blob.substr(HeaderSize, TotalFileSize - HeaderSize);
if (llvm::Error DecompressionError = llvm::compression::decompress(
CompressionFormat, llvm::arrayRefFromStringRef(CompressedData),
DecompressedData, UncompressedSize))
@@ -1331,32 +1346,64 @@ CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input,
// List bundle IDs. Return true if an error was found.
Error OffloadBundler::ListBundleIDsInFile(
StringRef InputFileName, const OffloadBundlerConfig &BundlerConfig) {
+
+ size_t Offset = 0;
+ size_t NextBundleStart = 0;
+ std::unique_ptr<MemoryBuffer> Buffer;
+
// Open Input file.
- ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
+ ErrorOr<std::unique_ptr<MemoryBuffer>> Contents =
MemoryBuffer::getFileOrSTDIN(InputFileName, /*IsText=*/true);
- if (std::error_code EC = CodeOrErr.getError())
+ if (std::error_code EC = Contents.getError())
return createFileError(InputFileName, EC);
- // Decompress the input if necessary.
- Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
- CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);
- if (!DecompressedBufferOrErr)
- return createStringError(
- inconvertibleErrorCode(),
- "Failed to decompress input: " +
- llvm::toString(DecompressedBufferOrErr.takeError()));
+ // There may be multiple bundles.
+ while ((NextBundleStart != StringRef::npos) &&
+ (Offset < (**Contents).getBufferSize())) {
+ Buffer = MemoryBuffer::getMemBuffer(
+ (**Contents).getBuffer().drop_front(Offset), "",
+ /*RequiresNullTerminator=*/false);
- MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr;
+ if (identify_magic((*Buffer).getBuffer()) ==
+ file_magic::offload_bundle_compressed) {
+ NextBundleStart = (*Buffer).getBuffer().find("CCOB", 4);
+ } else
+ NextBundleStart = StringRef::npos;
- // Select the right files handler.
- Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
- CreateFileHandler(DecompressedInput, BundlerConfig);
- if (!FileHandlerOrErr)
- return FileHandlerOrErr.takeError();
+ ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
+ MemoryBuffer::getMemBuffer(
+ (*Buffer).getBuffer().take_front(NextBundleStart),
+ InputFileName, // FileName,
+ false);
+ if (std::error_code EC = CodeOrErr.getError())
+ return createFileError(InputFileName, EC);
- std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
- assert(FH);
- return FH->listBundleIDs(DecompressedInput);
+ // Decompress the input if necessary.
+ Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
+ CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);
+ if (!DecompressedBufferOrErr)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Failed to decompress input: " +
+ llvm::toString(DecompressedBufferOrErr.takeError()));
+
+ MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr;
+
+ // Select the right files handler.
+ Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
+ CreateFileHandler(DecompressedInput, BundlerConfig);
+ if (!FileHandlerOrErr)
+ return FileHandlerOrErr.takeError();
+ std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
+ assert(FH);
+ Error E = FH->listBundleIDs(DecompressedInput);
+ if (E)
+ return E;
+
+ if (NextBundleStart != StringRef::npos)
+ Offset += NextBundleStart;
+ }
+ return Error::success();
}
/// @brief Checks if a code object \p CodeObjectInfo is compatible with a given
@@ -1539,30 +1586,6 @@ Error OffloadBundler::UnbundleFiles() {
if (std::error_code EC = CodeOrErr.getError())
return createFileError(BundlerConfig.InputFileNames.front(), EC);
- // Decompress the input if necessary.
- Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
- CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);
- if (!DecompressedBufferOrErr)
- return createStringError(
- inconvertibleErrorCode(),
- "Failed to decompress input: " +
- llvm::toString(DecompressedBufferOrErr.takeError()));
-
- MemoryBuffer &Input = **DecompressedBufferOrErr;
-
- // Select the right files handler.
- Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
- CreateFileHandler(Input, BundlerConfig);
- if (!FileHandlerOrErr)
- return FileHandlerOrErr.takeError();
-
- std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
- assert(FH);
-
- // Read the header of the bundled file.
- if (Error Err = FH->ReadHeader(Input))
- return Err;
-
// Create a work list that consist of the map triple/output file.
StringMap<StringRef> Worklist;
auto Output = BundlerConfig.OutputFileNames.begin();
@@ -1574,51 +1597,111 @@ Error OffloadBundler::UnbundleFiles() {
++Output;
}
- // Read all the bundles that are in the work list. If we find no bundles we
- // assume the file is meant for the host target.
+ // The input may contain multiple concatenated fat binary blobs (e.g. when
+ // the linker merges .hip_fatbin sections from multiple TUs into one). Walk
+ // through each blob exactly as ListBundleIDsInFile does, draining worklist
+ // entries as matching targets are found.
bool FoundHostBundle = false;
- while (!Worklist.empty()) {
- Expected<std::optional<StringRef>> CurTripleOrErr =
- FH->ReadBundleStart(Input);
- if (!CurTripleOrErr)
- return CurTripleOrErr.takeError();
-
- // We don't have more bundles.
- if (!*CurTripleOrErr)
- break;
-
- StringRef CurTriple = **CurTripleOrErr;
- assert(!CurTriple.empty());
- if (!checkOffloadBundleID(CurTriple))
- return createStringError(errc::invalid_argument,
- "invalid bundle id read from the bundle");
+ size_t Offset = 0;
+ size_t NextBundleStart = 0;
+ std::unique_ptr<MemoryBuffer> Buffer;
+
+ while ((NextBundleStart != StringRef::npos) &&
+ (Offset < (**CodeOrErr).getBufferSize())) {
+
+ Buffer = MemoryBuffer::getMemBuffer(
+ (**CodeOrErr).getBuffer().drop_front(Offset), "",
+ /*RequiresNullTerminator=*/false);
+
+ if (identify_magic((*Buffer).getBuffer()) ==
+ file_magic::offload_bundle_compressed) {
+ NextBundleStart = (*Buffer).getBuffer().find("CCOB", 4);
+ } else if (identify_magic((*Buffer).getBuffer()) ==
+ file_magic::offload_bundle) {
+ NextBundleStart = (*Buffer).getBuffer().find(
+ OFFLOAD_BUNDLER_MAGIC_STR, sizeof(OFFLOAD_BUNDLER_MAGIC_STR));
+ } else
+ NextBundleStart = StringRef::npos;
+
+ ErrorOr<std::unique_ptr<MemoryBuffer>> BlobOrErr =
+ MemoryBuffer::getMemBuffer(
+ (*Buffer).getBuffer().take_front(NextBundleStart),
+ BundlerConfig.InputFileNames.front(),
+ /*RequiresNullTerminator=*/false);
+ if (std::error_code EC = BlobOrErr.getError())
+ return createFileError(BundlerConfig.InputFileNames.front(), EC);
+
+ // Decompress the blob if necessary.
+ Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =
+ CompressedOffloadBundle::decompress(**BlobOrErr, BundlerConfig.Verbose);
+ if (!DecompressedBufferOrErr)
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Failed to decompress input: " +
+ llvm::toString(DecompressedBufferOrErr.takeError()));
+
+ MemoryBuffer &Input = **DecompressedBufferOrErr;
+
+ // Select the right file handler for this blob.
+ Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =
+ CreateFileHandler(Input, BundlerConfig);
+ if (!FileHandlerOrErr)
+ return FileHandlerOrErr.takeError();
+
+ std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;
+ assert(FH);
+
+ // Read the header of this blob.
+ if (Error Err = FH->ReadHeader(Input.getBuffer()))
+ return Err;
- auto Output = Worklist.begin();
- for (auto E = Worklist.end(); Output != E; Output++) {
- if (isCodeObjectCompatible(
- OffloadTargetInfo(CurTriple, BundlerConfig),
- OffloadTargetInfo((*Output).first(), BundlerConfig))) {
+ // Drain worklist entries satisfied by this blob.
+ while (!Worklist.empty()) {
+ Expected<std::optional<StringRef>> CurTripleOrErr =
+ FH->ReadBundleStart(Input.getBuffer());
+ if (!CurTripleOrErr)
+ return CurTripleOrErr.takeError();
+
+ // No more bundles in this blob.
+ if (!*CurTripleOrErr)
break;
+
+ StringRef CurTriple = **CurTripleOrErr;
+ assert(!CurTriple.empty());
+ if (!checkOffloadBundleID(CurTriple))
+ return createStringError(errc::invalid_argument,
+ "invalid bundle id read from the bundle");
+
+ auto Output = Worklist.begin();
+ for (auto E = Worklist.end(); Output != E; Output++) {
+ if (isCodeObjectCompatible(
+ OffloadTargetInfo(CurTriple, BundlerConfig),
+ OffloadTargetInfo((*Output).first(), BundlerConfig)))
+ break;
}
- }
- if (Output == Worklist.end())
- continue;
- // Check if the output file can be opened and copy the bundle to it.
- std::error_code EC;
- raw_fd_ostream OutputFile((*Output).second, EC, sys::fs::OF_None);
- if (EC)
- return createFileError((*Output).second, EC);
- if (Error Err = FH->ReadBundle(OutputFile, Input))
- return Err;
- if (Error Err = FH->ReadBundleEnd(Input))
- return Err;
- Worklist.erase(Output);
+ if (Output == Worklist.end())
+ continue;
+
+ // Check if the output file can be opened and copy the bundle to it.
+ std::error_code EC;
+ raw_fd_ostream OutputFile((*Output).second, EC, sys::fs::OF_None);
+ if (EC)
+ return createFileError((*Output).second, EC);
+ if (Error Err = FH->ReadBundle(OutputFile, Input))
+ return Err;
+ if (Error Err = FH->ReadBundleEnd(Input))
+ return Err;
+ Worklist.erase(Output);
+
+ // Record if we found the host bundle.
+ auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig);
+ if (OffloadInfo.hasHostKind())
+ FoundHostBundle = true;
+ }
- // Record if we found the host bundle.
- auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig);
- if (OffloadInfo.hasHostKind())
- FoundHostBundle = true;
+ if (NextBundleStart != StringRef::npos)
+ Offset += NextBundleStart;
}
if (!BundlerConfig.AllowMissingBundles && !Worklist.empty()) {
@@ -1654,7 +1737,8 @@ Error OffloadBundler::UnbundleFiles() {
// because the entire WorkList has been checked above.
auto OffloadInfo = OffloadTargetInfo(E.getKey(), BundlerConfig);
if (OffloadInfo.hasHostKind())
- OutputFile.write(Input.getBufferStart(), Input.getBufferSize());
+ OutputFile.write((**CodeOrErr).getBufferStart(),
+ (**CodeOrErr).getBufferSize());
}
return Error::success();
}
@@ -1863,11 +1947,11 @@ Error OffloadBundler::UnbundleArchive() {
assert(FileHandler &&
"FileHandle creation failed for file in the archive!");
- if (Error ReadErr = FileHandler->ReadHeader(CodeObjectBuffer))
+ if (Error ReadErr = FileHandler->ReadHeader(CodeObjectBuffer.getBuffer()))
return ReadErr;
Expected<std::optional<StringRef>> CurBundleIDOrErr =
- FileHandler->ReadBundleStart(CodeObjectBuffer);
+ FileHandler->ReadBundleStart(CodeObjectBuffer.getBuffer());
if (!CurBundleIDOrErr)
return CurBundleIDOrErr.takeError();
@@ -1923,7 +2007,7 @@ Error OffloadBundler::UnbundleArchive() {
return Err;
Expected<std::optional<StringRef>> NextTripleOrErr =
- FileHandler->ReadBundleStart(CodeObjectBuffer);
+ FileHandler->ReadBundleStart(CodeObjectBuffer.getBuffer());
if (!NextTripleOrErr)
return NextTripleOrErr.takeError();
diff --git a/clang/test/Driver/clang-offload-bundler-multi-compress.c b/clang/test/Driver/clang-offload-bundler-multi-compress.c
new file mode 100644
index 0000000000000..859d3e5773e6b
--- /dev/null
+++ b/clang/test/Driver/clang-offload-bundler-multi-compress.c
@@ -0,0 +1,187 @@
+// REQUIRES: x86-registered-target
+// REQUIRES: zlib || zstd
+// UNSUPPORTED: target={{.*}}-darwin{{.*}}, target={{.*}}-aix{{.*}}, target={{.*}}-zos{{.*}}
+
+// Tests that clang-offload-bundler --list correctly enumerates all bundle IDs
+// from multiple concatenated compressed (CCOB) fat binary blobs stored in the
+// .hip_fatbin section of an ELF object. This models the layout produced when
+// a shared library or relocatable object is linked from multiple HIP
+// translation units, each of which contributes its own CCOB blob to the
+// .hip_fatbin section.
+
+//
+// Create device content files for two simulated translation units.
+//
+// RUN: echo 'Content of device file 1' > %t.dev1
+// RUN: echo 'Content of device file 2' > %t.dev2
+// RUN: echo 'Content of device file 3' > %t.dev3
+
+//
+// Produce two compressed fat binary blobs with distinct GPU targets so that
+// the FileCheck assertions below are unambiguous.
+//
+// Bundle 1: gfx906 + gfx908
+// RUN: clang-offload-bundler -compress -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx908 \
+// RUN: -input=%t.dev1 -input=%t.dev2 \
+// RUN: -output=%t.bundle1.ccob
+
+// Bundle 2: gfx1030 + gfx1100
+// RUN: clang-offload-bundler -compress -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx1030,hip-amdgcn-amd-amdhsa--gfx1100 \
+// RUN: -input=%t.dev1 -input=%t.dev2 \
+// RUN: -output=%t.bundle2.ccob
+
+// Bundle 3: gfx942 + gfx1201
+// RUN: clang-offload-bundler -compress -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx942,hip-amdgcn-amd-amdhsa--gfx1201 \
+// RUN: -input=%t.dev1 -input=%t.dev3 \
+// RUN: -output=%t.bundle3.ccob
+
+//
+// Baseline: --list on each individual compressed bundle must work.
+//
+// RUN: clang-offload-bundler -type=bc -list -input=%t.bundle1.ccob \
+// RUN: | FileCheck %s --check-prefix=SINGLE1
+// SINGLE1-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// SINGLE1-DAG: hip-amdgcn-amd-amdhsa--gfx908
+
+// RUN: clang-offload-bundler -type=bc -list -input=%t.bundle2.ccob \
+// RUN: | FileCheck %s --check-prefix=SINGLE2
+// SINGLE2-DAG: hip-amdgcn-amd-amdhsa--gfx1030
+// SINGLE2-DAG: hip-amdgcn-amd-amdhsa--gfx1100
+
+//
+// Concatenate the two CCOB blobs. This mirrors what the linker does when it
+// merges the .hip_fatbin input sections contributed by multiple TUs.
+//
+// RUN: cat %t.bundle1.ccob %t.bundle2.ccob > %t.multi.fatbin
+
+//
+// Build a host object and inject the concatenated blob as its .hip_fatbin
+// section, replicating the ELF layout of a fat shared library.
+//
+// RUN: %clang -O0 -target %itanium_abi_triple %s -c -o %t.host.o
+// RUN: llvm-objcopy \
+// RUN: --add-section=.hip_fatbin=%t.multi.fatbin \
+// RUN: --set-section-flags=.hip_fatbin=alloc \
+// RUN: %t.host.o %t.multi.o
+
+//
+// --list on an object whose .hip_fatbin section contains two concatenated
+// CCOB blobs must enumerate all bundle IDs from both fat binaries.
+//
+// RUN: clang-offload-bundler -type=o -list -input=%t.multi.fatbin \
+// RUN: | FileCheck %s --check-prefix=MULTI
+// MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx908
+// MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx1030
+// MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx1100
+
+//
+// Concatenate three CCOB blobs to verify that the loop correctly processes
+// 3+ blobs without premature termination.
+//
+// RUN: cat %t.bundle1.ccob %t.bundle2.ccob %t.bundle3.ccob > %t.triple.fatbin
+
+// --list on three concatenated CCOB blobs must enumerate all bundle IDs.
+// RUN: clang-offload-bundler -type=o -list -input=%t.triple.fatbin \
+// RUN: | FileCheck %s --check-prefix=TRIPLE
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx908
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1030
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1100
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx942
+// TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1201
+
+// --unbundle must extract targets spanning all three CCOB blobs.
+// RUN: clang-offload-bundler -type=o -unbundle \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx1030,hip-amdgcn-amd-amdhsa--gfx942 \
+// RUN: -output=%t.tri.res.gfx906 -output=%t.tri.res.gfx1030 -output=%t.tri.res.gfx942 \
+// RUN: -input=%t.triple.fatbin
+// RUN:
diff %t.dev1 %t.tri.res.gfx906
+// RUN:
diff %t.dev1 %t.tri.res.gfx1030
+// RUN:
diff %t.dev1 %t.tri.res.gfx942
+
+//
+// ===--- Uncompressed multi-bundle tests ---===
+//
+// Repeat the same --list and --unbundle tests using uncompressed fat binary
+// blobs (__CLANG_OFFLOAD_BUNDLE__ binary format without CCOB).
+//
+
+// Bundle 1 (uncompressed): gfx906 + gfx908
+// RUN: clang-offload-bundler -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx908 \
+// RUN: -input=%t.dev1 -input=%t.dev2 \
+// RUN: -output=%t.unc.bundle1.bc
+
+// Bundle 2 (uncompressed): gfx1030 + gfx1100
+// RUN: clang-offload-bundler -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx1030,hip-amdgcn-amd-amdhsa--gfx1100 \
+// RUN: -input=%t.dev1 -input=%t.dev2 \
+// RUN: -output=%t.unc.bundle2.bc
+
+// Bundle 3 (uncompressed): gfx942 + gfx1201
+// RUN: clang-offload-bundler -type=bc \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx942,hip-amdgcn-amd-amdhsa--gfx1201 \
+// RUN: -input=%t.dev1 -input=%t.dev3 \
+// RUN: -output=%t.unc.bundle3.bc
+
+// Concatenate the two uncompressed blobs.
+// RUN: cat %t.unc.bundle1.bc %t.unc.bundle2.bc > %t.unc.multi.fatbin
+
+// --list must enumerate all bundle IDs from both uncompressed blobs.
+// RUN: clang-offload-bundler -type=o -list -input=%t.unc.multi.fatbin \
+// RUN: | FileCheck %s --check-prefix=UNC-MULTI
+// UNC-MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// UNC-MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx908
+// UNC-MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx1030
+// UNC-MULTI-DAG: hip-amdgcn-amd-amdhsa--gfx1100
+
+// --unbundle must extract targets spanning both uncompressed blobs.
+// RUN: clang-offload-bundler -type=o -unbundle \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx1100 \
+// RUN: -output=%t.unc.res.gfx906 -output=%t.unc.res.gfx1100 \
+// RUN: -input=%t.unc.multi.fatbin
+// RUN:
diff %t.dev1 %t.unc.res.gfx906
+// RUN:
diff %t.dev2 %t.unc.res.gfx1100
+
+// Concatenate three uncompressed blobs.
+// RUN: cat %t.unc.bundle1.bc %t.unc.bundle2.bc %t.unc.bundle3.bc > %t.unc.triple.fatbin
+
+// --list on three concatenated uncompressed blobs must enumerate all bundle IDs.
+// RUN: clang-offload-bundler -type=o -list -input=%t.unc.triple.fatbin \
+// RUN: | FileCheck %s --check-prefix=UNC-TRIPLE
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx906
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx908
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1030
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1100
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx942
+// UNC-TRIPLE-DAG: hip-amdgcn-amd-amdhsa--gfx1201
+
+// --unbundle must extract targets spanning all three uncompressed blobs.
+// RUN: clang-offload-bundler -type=o -unbundle \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx1030,hip-amdgcn-amd-amdhsa--gfx942 \
+// RUN: -output=%t.unc.tri.res.gfx906 -output=%t.unc.tri.res.gfx1030 -output=%t.unc.tri.res.gfx942 \
+// RUN: -input=%t.unc.triple.fatbin
+// RUN:
diff %t.dev1 %t.unc.tri.res.gfx906
+// RUN:
diff %t.dev1 %t.unc.tri.res.gfx1030
+// RUN:
diff %t.dev1 %t.unc.tri.res.gfx942
+
+//
+// --unbundle on the same concatenated CCOB file must correctly extract targets
+// that span both blobs in a single call. gfx906 comes from bundle1 and gfx1100
+// comes from bundle2, so this exercises cross-blob extraction.
+//
+// RUN: clang-offload-bundler -type=o -unbundle \
+// RUN: -targets=hip-amdgcn-amd-amdhsa--gfx906,hip-amdgcn-amd-amdhsa--gfx1100 \
+// RUN: -output=%t.res.gfx906 -output=%t.res.gfx1100 \
+// RUN: -input=%t.multi.fatbin
+// RUN:
diff %t.dev1 %t.res.gfx906
+// RUN:
diff %t.dev2 %t.res.gfx1100
+
+// Some code so that we can compile this file as a host object.
+int A = 0;
+void test_func(void) { ++A; }
+
More information about the cfe-commits
mailing list