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
