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; }
+


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to