[llvm] [llvm][tools] Add support to llvm-offload-binary to unbundle spirv64-intel images (PR #184774)

Alex Duran via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 9 10:14:10 PDT 2026


https://github.com/adurang updated https://github.com/llvm/llvm-project/pull/184774

>From 85d9675c73d55b113755686591d7e5a687903373 Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Thu, 5 Mar 2026 03:22:11 -0800
Subject: [PATCH 1/5] [llvm][tools] Add support to llvm-offload-binary to
 unbundle spirv embedded images

---
 .../docs/CommandGuide/llvm-offload-binary.rst | 113 ++++++++++++++-
 .../llvm-offload-binary.ll                    |   6 +
 .../llvm-offload-binary.cpp                   | 130 +++++++++++++++++-
 3 files changed, 237 insertions(+), 12 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-offload-binary.rst b/llvm/docs/CommandGuide/llvm-offload-binary.rst
index 960b12d8af286..a018c355e2410 100644
--- a/llvm/docs/CommandGuide/llvm-offload-binary.rst
+++ b/llvm/docs/CommandGuide/llvm-offload-binary.rst
@@ -15,7 +15,24 @@ DESCRIPTION
 files into a single binary container. The resulting binary can then be embedded
 into the host section table to form a fat binary containing offloading code for
 different targets. Conversely, it can also extract previously bundled device
-images.
+images from offload binaries.
+
+**Extraction modes:**
+
+- **Extract all images**: When no ``--image`` filters are specified, all offload
+  images are automatically extracted with descriptive filenames.
+- **Extract filtered images**: When ``--image`` filters are specified, only
+  matching images are extracted.
+
+**SPIR-V support:**
+
+For Intel SPIR-V targets (``spirv64-intel`` and ``spirv32-intel``), the tool
+provides automatic extraction:
+
+- **Raw SPIR-V**: Detected using file magic and extracted with ``.spv`` extension
+- **ELF-wrapped SPIR-V**: Extracts both the ELF wrapper (``.elf``) and embedded
+  SPIR-V binaries from sections named ``__openmp_offload_spirv_*`` (``.spv``)
+- **Unknown format**: Extracted with ``.bin`` extension for inspection
 
 The binary format begins with the magic bytes ``0x10FF10AD``, followed by a
 version and size. Each binary contains its own header, allowing tools to locate
@@ -32,12 +49,24 @@ EXAMPLE
   $ llvm-offload-binary -o out.bin \
         --image=file=input.o,triple=nvptx64,arch=sm_70
 
-  # Extract a matching image from a fat binary:
-  $ llvm-offload-binary in.bin \
-        --image=file=output.o,triple=nvptx64,arch=sm_70
+  # Extract all offload images from an executable (no filters):
+  $ llvm-offload-binary in.bin
+  # Output:
+  # Extracted: in-nvptx64-nvidia-cuda-sm_70.0.bc
+  # Extracted (ELF wrapper): in-spirv64-intel.0.elf
+  # Extracted SPIR-V: in-spirv64-intel.0_0.spv
 
-  # Extract and archive images into a static library:
-  $ llvm-offload-binary in.bin --archive -o libdevice.a
+  # Extract only SPIR-V images using filters:
+  $ llvm-offload-binary in.bin --image=triple=spirv64-intel
+  # Output:
+  # Extracted (ELF wrapper): in-spirv64-intel.0.elf
+  # Extracted SPIR-V: in-spirv64-intel.0_0.spv
+
+  # Extract filtered images to a specific file:
+  $ llvm-offload-binary in.bin --image=file=output.bc,arch=sm_70
+
+  # Extract filtered images to an archive:
+  $ llvm-offload-binary in.bin --image=file=output.a,triple=nvptx64 --archive
 
 OPTIONS
 -------
@@ -179,7 +208,77 @@ The enumerated values for ``image kind`` and ``offload kind`` are:
    | OFK_SYCL   | 0x04  | The producer was SYCL                 |
    +------------+-------+---------------------------------------+
 
+COMMON WORKFLOWS
+----------------
+
+**Workflow 1: Explore Executable Contents**
+
+Extract all embedded offload images to see what's inside:
+
+.. code-block:: console
+
+  $ clang++ -fopenmp -fopenmp-targets=nvptx64,spirv64-intel app.cpp -o myapp
+  $ llvm-offload-binary myapp
+  # Output:
+  # Extracted: myapp-nvptx64-nvidia-cuda-sm_70.0.bc
+  # Extracted (ELF wrapper): myapp-spirv64-intel.1.elf
+  # Extracted SPIR-V: myapp-spirv64-intel.1_0.spv
+
+**Workflow 2: Extract Specific Target**
+
+Extract only images for a specific target:
+
+.. code-block:: console
+
+  $ llvm-offload-binary myapp --image=triple=spirv64-intel
+  # Output:
+  # Extracted (ELF wrapper): myapp-spirv64-intel.0.elf
+  # Extracted SPIR-V: myapp-spirv64-intel.0_0.spv
+
+**Workflow 3: Create Device Image Archive**
+
+Extract filtered images into a static archive:
+
+.. code-block:: console
+
+  $ llvm-offload-binary myapp --image=file=nvptx.a,triple=nvptx64 --archive
+  $ ar t nvptx.a
+  # Shows extracted CUDA images
+
+**Workflow 4: Validate SPIR-V**
+
+Extract and validate SPIR-V binaries:
+
+.. code-block:: console
+
+  $ llvm-offload-binary myapp --image=triple=spirv64-intel
+  $ spirv-val myapp-spirv64-intel.0_0.spv
+  $ spirv-dis myapp-spirv64-intel.0_0.spv -o kernel.spvasm
+
+**Workflow 5: Bundle Multiple Targets**
+
+Create a fat binary from multiple device images:
+
+.. code-block:: console
+
+  $ clang++ -fopenmp -fopenmp-targets=nvptx64 --offload-device-only kernel.cpp -o kernel_nvptx.bc
+  $ clang++ -fopenmp -fopenmp-targets=spirv64-intel --offload-device-only kernel.cpp -o kernel_spirv.bc
+  $ llvm-offload-binary -o bundle.bin \
+      --image=file=kernel_nvptx.bc,triple=nvptx64,arch=sm_70 \
+      --image=file=kernel_spirv.bc,triple=spirv64-intel
+
+**Workflow 6: Extract and Rebundle**
+
+Extract images from one binary and rebundle with modifications:
+
+.. code-block:: console
+
+  $ llvm-offload-binary old_app
+  $ llvm-offload-binary -o new_bundle.bin \
+      --image=file=old_app-nvptx64-nvidia-cuda-sm_70.0.bc,triple=nvptx64,arch=sm_70 \
+      --image=file=new_kernel.bc,triple=nvptx64,arch=sm_80
+
 SEE ALSO
 --------
 
-:manpage:`clang(1)`, :manpage:`llvm-objdump(1)`
+:manpage:`clang(1)`, :manpage:`llvm-objdump(1)`, :manpage:`spirv-val(1)`, :manpage:`spirv-dis(1)`
diff --git a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
index df46ad3a0d38a..3b835c24cd50e 100644
--- a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
+++ b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
@@ -15,3 +15,9 @@
 ; RUN: llvm-offload-binary -o %t3 --image=file=%s
 ; RUN: llvm-offload-binary %t3 --image=file=%t4
 ; RUN: diff %s %t4
+
+; Test extracting all images without specifying --image filters
+; RUN: llvm-offload-binary %t
+; RUN: ls  llvm-offload-binary.*-x-y-z-abc.0.| FileCheck --check-prefix=EXTRACT %s
+
+; EXTRACT: llvm-offload-binary.{{.*}}-x-y-z-abc.0.
diff --git a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
index e22d13b946651..ceaa7e7f0b04b 100644
--- a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
+++ b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
@@ -16,6 +16,8 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/BinaryFormat/Magic.h"
 #include "llvm/Object/ArchiveWriter.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Object/ObjectFile.h"
 #include "llvm/Object/OffloadBinary.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FileOutputBuffer.h"
@@ -135,6 +137,116 @@ static Error bundleImages() {
   return Error::success();
 }
 
+// Extract SPIR-V binaries from an ELF image with triple "spirv64-intel" or
+// "spirv32-intel". These ELF images contain SPIR-V binaries in sections named
+// "__openmp_offload_spirv_*".
+static Expected<SmallVector<StringRef>>
+extractSPIRVFromELF(StringRef ImageData) {
+  SmallVector<StringRef> SPIRVBinaries;
+
+  // Try to parse as ELF object file
+  Expected<std::unique_ptr<ObjectFile>> ObjOrErr =
+      ObjectFile::createObjectFile(MemoryBufferRef(ImageData, "spirv-elf"));
+  if (!ObjOrErr)
+    return ObjOrErr.takeError();
+
+  ObjectFile &Obj = *ObjOrErr->get();
+  if (!Obj.isELF())
+    return createStringError(inconvertibleErrorCode(),
+                             "Expected ELF format for Intel SPIR-V image");
+
+  // Extract all sections with name matching "__openmp_offload_spirv_*"
+  for (const SectionRef &Sec : Obj.sections()) {
+    Expected<StringRef> NameOrErr = Sec.getName();
+    if (!NameOrErr)
+      continue;
+
+    if (!NameOrErr->starts_with("__openmp_offload_spirv_"))
+      continue;
+
+    Expected<StringRef> ContentsOrErr = Sec.getContents();
+    if (!ContentsOrErr)
+      return ContentsOrErr.takeError();
+
+    SPIRVBinaries.push_back(*ContentsOrErr);
+  }
+
+  if (SPIRVBinaries.empty())
+    return createStringError(inconvertibleErrorCode(),
+                             "No SPIR-V sections found in ELF image");
+
+  return SPIRVBinaries;
+}
+
+// Helper function to extract a single binary image, with SPIR-V support.
+// Returns Error on failure.
+static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
+                           uint64_t Idx, StringSaver &Saver) {
+  // Check if this is a SPIR-V image that needs special handling
+  if (Binary->getTriple().starts_with("spirv64-intel")) {
+    StringRef ImageData = Binary->getImage();
+    std::string BaseFilename =
+        sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
+    StringRef Arch = Binary->getArch();
+    if (!Arch.empty())
+      BaseFilename += "-" + Arch.str();
+    BaseFilename += "." + std::to_string(Idx);
+
+    // Check if the image is already raw SPIR-V (not ELF-wrapped)
+    if (identify_magic(ImageData) == file_magic::spirv_object) {
+      // Image is already SPIR-V, just extract it with .spv extension
+      StringRef Filename = Saver.save(BaseFilename + ".spv");
+      if (Error E = writeFile(Filename, ImageData))
+        return E;
+      outs() << "Extracted SPIR-V: " << Filename << "\n";
+      return Error::success();
+    }
+
+    // Try to parse as ELF and extract SPIR-V from sections
+    auto SPIRVBinariesOrErr = extractSPIRVFromELF(ImageData);
+    if (!SPIRVBinariesOrErr) {
+      // Not ELF or no SPIR-V sections, extract as-is with .bin extension
+      StringRef Filename = Saver.save(BaseFilename + ".bin");
+      if (Error E = writeFile(Filename, ImageData))
+        return E;
+      outs() << "Extracted (unknown format): " << Filename << "\n";
+      return Error::success();
+    }
+
+    // Successfully extracted SPIR-V from ELF
+    // Extract the ELF wrapper
+    StringRef ELFFilename = Saver.save(BaseFilename + ".elf");
+    if (Error E = writeFile(ELFFilename, ImageData))
+      return E;
+    outs() << "Extracted (ELF wrapper): " << ELFFilename << "\n";
+
+    // Extract each SPIR-V binary found in the ELF
+    uint64_t SPIRVIdx = 0;
+    for (StringRef SPIRVBinary : *SPIRVBinariesOrErr) {
+      StringRef Filename =
+          Saver.save(BaseFilename + "_" + std::to_string(SPIRVIdx++) + ".spv");
+      if (Error E = writeFile(Filename, SPIRVBinary))
+        return E;
+      outs() << "Extracted SPIR-V: " << Filename << "\n";
+    }
+  } else {
+    // Regular extraction (non-SPIR-V)
+    std::string Filename =
+        sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
+    StringRef Arch = Binary->getArch();
+    if (!Arch.empty())
+      Filename += "-" + Arch.str();
+    Filename += "." + std::to_string(Idx) + "." +
+                getImageKindName(Binary->getImageKind()).str();
+
+    if (Error E = writeFile(Saver.save(Filename), Binary->getImage()))
+      return E;
+    outs() << "Extracted: " << Filename << "\n";
+  }
+
+  return Error::success();
+}
+
 static Error unbundleImages() {
   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
       MemoryBuffer::getFileOrSTDIN(InputFile);
@@ -152,6 +264,18 @@ static Error unbundleImages() {
   if (Error Err = extractOffloadBinaries(*Buffer, Binaries))
     return Err;
 
+  // If no filters specified, extract all images
+  if (DeviceImages.empty()) {
+    BumpPtrAllocator Alloc;
+    StringSaver Saver(Alloc);
+    uint64_t Idx = 0;
+    for (const OffloadFile &File : Binaries) {
+      if (Error E = extractBinary(File.getBinary(), InputFile, Idx++, Saver))
+        return E;
+    }
+    return Error::success();
+  }
+
   // Try to extract each device image specified by the user from the input file.
   for (StringRef Image : DeviceImages) {
     BumpPtrAllocator Alloc;
@@ -202,11 +326,7 @@ static Error unbundleImages() {
     } else {
       uint64_t Idx = 0;
       for (const OffloadBinary *Binary : Extracted) {
-        StringRef Filename =
-            Saver.save(sys::path::stem(InputFile) + "-" + Binary->getTriple() +
-                       "-" + Binary->getArch() + "." + std::to_string(Idx++) +
-                       "." + getImageKindName(Binary->getImageKind()));
-        if (Error E = writeFile(Filename, Binary->getImage()))
+        if (Error E = extractBinary(Binary, InputFile, Idx++, Saver))
           return E;
       }
     }

>From 5812ceee44483c794af8dfb9753272fa1a1e3e8c Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Thu, 5 Mar 2026 04:32:10 -0800
Subject: [PATCH 2/5] sigthly modify test

---
 llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
index 3b835c24cd50e..525745be9ce52 100644
--- a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
+++ b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
@@ -17,7 +17,6 @@
 ; RUN: diff %s %t4
 
 ; Test extracting all images without specifying --image filters
-; RUN: llvm-offload-binary %t
-; RUN: ls  llvm-offload-binary.*-x-y-z-abc.0.| FileCheck --check-prefix=EXTRACT %s
+; RUN: llvm-offload-binary %t | FileCheck --check-prefix=EXTRACT %s
 
-; EXTRACT: llvm-offload-binary.{{.*}}-x-y-z-abc.0.
+; EXTRACT: Extracted: llvm-offload-binary.{{.*}}-x-y-z-abc.0.

>From 05fb3a59f42da4ab220a90d918145a4b321dd3b1 Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Thu, 5 Mar 2026 14:47:35 -0800
Subject: [PATCH 3/5] consume error

---
 llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
index ceaa7e7f0b04b..8f94ee5e5be20 100644
--- a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
+++ b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
@@ -158,8 +158,11 @@ extractSPIRVFromELF(StringRef ImageData) {
   // Extract all sections with name matching "__openmp_offload_spirv_*"
   for (const SectionRef &Sec : Obj.sections()) {
     Expected<StringRef> NameOrErr = Sec.getName();
-    if (!NameOrErr)
+    if (!NameOrErr) {
+      // consume error and skip this section
+      consumeError(NameOrErr.takeError());
       continue;
+    }
 
     if (!NameOrErr->starts_with("__openmp_offload_spirv_"))
       continue;

>From 7e831aba4670caee9350afa6e54c15df19b4fba8 Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Mon, 9 Mar 2026 09:04:53 -0700
Subject: [PATCH 4/5] address reviews

---
 .../docs/CommandGuide/llvm-offload-binary.rst |  25 ++-
 .../llvm-offload-binary.ll                    |   2 +-
 .../llvm-offload-binary.cpp                   | 146 ++++++++++--------
 3 files changed, 88 insertions(+), 85 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-offload-binary.rst b/llvm/docs/CommandGuide/llvm-offload-binary.rst
index a018c355e2410..cee0f77ba8980 100644
--- a/llvm/docs/CommandGuide/llvm-offload-binary.rst
+++ b/llvm/docs/CommandGuide/llvm-offload-binary.rst
@@ -17,22 +17,9 @@ into the host section table to form a fat binary containing offloading code for
 different targets. Conversely, it can also extract previously bundled device
 images from offload binaries.
 
-**Extraction modes:**
-
-- **Extract all images**: When no ``--image`` filters are specified, all offload
-  images are automatically extracted with descriptive filenames.
-- **Extract filtered images**: When ``--image`` filters are specified, only
-  matching images are extracted.
-
-**SPIR-V support:**
-
-For Intel SPIR-V targets (``spirv64-intel`` and ``spirv32-intel``), the tool
-provides automatic extraction:
-
-- **Raw SPIR-V**: Detected using file magic and extracted with ``.spv`` extension
-- **ELF-wrapped SPIR-V**: Extracts both the ELF wrapper (``.elf``) and embedded
-  SPIR-V binaries from sections named ``__openmp_offload_spirv_*`` (``.spv``)
-- **Unknown format**: Extracted with ``.bin`` extension for inspection
+When extracting images, if no :option:`--image` filters are specified, all
+offload images are automatically extracted with descriptive filenames. When
+:option:`--image` filters are provided, only matching images are extracted.
 
 The binary format begins with the magic bytes ``0x10FF10AD``, followed by a
 version and size. Each binary contains its own header, allowing tools to locate
@@ -82,6 +69,12 @@ OPTIONS
   Commonly used optional keys include ``arch`` (e.g. ``sm_70`` for CUDA) and
   ``triple`` (e.g. nvptx64-nvidia-cuda).
 
+  When bundling, this option specifies images to include in the output binary.
+  When extracting, this option acts as a filter: only images matching the
+  specified keys are extracted. If no :option:`--image` options are provided
+  during extraction, all images are automatically extracted with descriptive
+  filenames.
+
 .. option:: -o <file>
 
   Write output to <file>. When bundling, this specifies the fat binary filename.
diff --git a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
index 525745be9ce52..e410e033baa73 100644
--- a/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
+++ b/llvm/test/tools/llvm-offload-binary/llvm-offload-binary.ll
@@ -16,7 +16,7 @@
 ; RUN: llvm-offload-binary %t3 --image=file=%t4
 ; RUN: diff %s %t4
 
-; Test extracting all images without specifying --image filters
+; Test extracting all images without specifying --image filters.
 ; RUN: llvm-offload-binary %t | FileCheck --check-prefix=EXTRACT %s
 
 ; EXTRACT: Extracted: llvm-offload-binary.{{.*}}-x-y-z-abc.0.
diff --git a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
index 8f94ee5e5be20..a1e09d34f2836 100644
--- a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
+++ b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
@@ -144,7 +144,7 @@ static Expected<SmallVector<StringRef>>
 extractSPIRVFromELF(StringRef ImageData) {
   SmallVector<StringRef> SPIRVBinaries;
 
-  // Try to parse as ELF object file
+  // Try to parse as ELF object file.
   Expected<std::unique_ptr<ObjectFile>> ObjOrErr =
       ObjectFile::createObjectFile(MemoryBufferRef(ImageData, "spirv-elf"));
   if (!ObjOrErr)
@@ -152,14 +152,13 @@ extractSPIRVFromELF(StringRef ImageData) {
 
   ObjectFile &Obj = *ObjOrErr->get();
   if (!Obj.isELF())
-    return createStringError(inconvertibleErrorCode(),
-                             "Expected ELF format for Intel SPIR-V image");
+    return createStringError("expected ELF format for Intel SPIR-V image");
 
-  // Extract all sections with name matching "__openmp_offload_spirv_*"
+  // Extract all sections with name matching "__openmp_offload_spirv_*".
   for (const SectionRef &Sec : Obj.sections()) {
     Expected<StringRef> NameOrErr = Sec.getName();
     if (!NameOrErr) {
-      // consume error and skip this section
+      // Consume error and skip this section.
       consumeError(NameOrErr.takeError());
       continue;
     }
@@ -175,81 +174,92 @@ extractSPIRVFromELF(StringRef ImageData) {
   }
 
   if (SPIRVBinaries.empty())
-    return createStringError(inconvertibleErrorCode(),
-                             "No SPIR-V sections found in ELF image");
+    return createStringError("no SPIR-V sections found in ELF image");
 
   return SPIRVBinaries;
 }
 
-// Helper function to extract a single binary image, with SPIR-V support.
-// Returns Error on failure.
-static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
-                           uint64_t Idx, StringSaver &Saver) {
-  // Check if this is a SPIR-V image that needs special handling
-  if (Binary->getTriple().starts_with("spirv64-intel")) {
-    StringRef ImageData = Binary->getImage();
-    std::string BaseFilename =
-        sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
-    StringRef Arch = Binary->getArch();
-    if (!Arch.empty())
-      BaseFilename += "-" + Arch.str();
-    BaseFilename += "." + std::to_string(Idx);
-
-    // Check if the image is already raw SPIR-V (not ELF-wrapped)
-    if (identify_magic(ImageData) == file_magic::spirv_object) {
-      // Image is already SPIR-V, just extract it with .spv extension
-      StringRef Filename = Saver.save(BaseFilename + ".spv");
-      if (Error E = writeFile(Filename, ImageData))
-        return E;
-      outs() << "Extracted SPIR-V: " << Filename << "\n";
-      return Error::success();
-    }
-
-    // Try to parse as ELF and extract SPIR-V from sections
-    auto SPIRVBinariesOrErr = extractSPIRVFromELF(ImageData);
-    if (!SPIRVBinariesOrErr) {
-      // Not ELF or no SPIR-V sections, extract as-is with .bin extension
-      StringRef Filename = Saver.save(BaseFilename + ".bin");
-      if (Error E = writeFile(Filename, ImageData))
-        return E;
-      outs() << "Extracted (unknown format): " << Filename << "\n";
-      return Error::success();
-    }
+// Extract a SPIR-V binary image (spirv64-intel or spirv32-intel).
+static Error extractSPIRVBinary(const OffloadBinary *Binary,
+                                StringRef InputFile, uint64_t Idx,
+                                StringSaver &Saver) {
+  StringRef ImageData = Binary->getImage();
+  std::string BaseFilename =
+      sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
+  StringRef Arch = Binary->getArch();
+  if (!Arch.empty())
+    BaseFilename += "-" + Arch.str();
+  BaseFilename += "." + std::to_string(Idx);
+
+  // Check if the image is already raw SPIR-V (not ELF-wrapped).
+  if (identify_magic(ImageData) == file_magic::spirv_object) {
+    // Image is already SPIR-V, just extract it with .spv extension.
+    StringRef Filename = Saver.save(BaseFilename + ".spv");
+    if (Error E = writeFile(Filename, ImageData))
+      return E;
+    outs() << "Extracted SPIR-V: " << Filename << "\n";
+    return Error::success();
+  }
 
-    // Successfully extracted SPIR-V from ELF
-    // Extract the ELF wrapper
-    StringRef ELFFilename = Saver.save(BaseFilename + ".elf");
-    if (Error E = writeFile(ELFFilename, ImageData))
+  // Try to parse as ELF and extract SPIR-V from sections.
+  auto SPIRVBinariesOrErr = extractSPIRVFromELF(ImageData);
+  if (!SPIRVBinariesOrErr) {
+    // Not ELF or no SPIR-V sections, extract as-is with .bin extension.
+    StringRef Filename = Saver.save(BaseFilename + ".bin");
+    if (Error E = writeFile(Filename, ImageData))
       return E;
-    outs() << "Extracted (ELF wrapper): " << ELFFilename << "\n";
-
-    // Extract each SPIR-V binary found in the ELF
-    uint64_t SPIRVIdx = 0;
-    for (StringRef SPIRVBinary : *SPIRVBinariesOrErr) {
-      StringRef Filename =
-          Saver.save(BaseFilename + "_" + std::to_string(SPIRVIdx++) + ".spv");
-      if (Error E = writeFile(Filename, SPIRVBinary))
-        return E;
-      outs() << "Extracted SPIR-V: " << Filename << "\n";
-    }
-  } else {
-    // Regular extraction (non-SPIR-V)
-    std::string Filename =
-        sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
-    StringRef Arch = Binary->getArch();
-    if (!Arch.empty())
-      Filename += "-" + Arch.str();
-    Filename += "." + std::to_string(Idx) + "." +
-                getImageKindName(Binary->getImageKind()).str();
-
-    if (Error E = writeFile(Saver.save(Filename), Binary->getImage()))
+    outs() << "Extracted (unknown format): " << Filename << "\n";
+    return Error::success();
+  }
+
+  // Successfully extracted SPIR-V from ELF.
+  // Extract the ELF wrapper.
+  StringRef ELFFilename = Saver.save(BaseFilename + ".elf");
+  if (Error E = writeFile(ELFFilename, ImageData))
+    return E;
+  outs() << "Extracted (ELF wrapper): " << ELFFilename << "\n";
+
+  // Extract each SPIR-V binary found in the ELF.
+  uint64_t SPIRVIdx = 0;
+  for (StringRef SPIRVBinary : *SPIRVBinariesOrErr) {
+    StringRef Filename =
+        Saver.save(BaseFilename + "_" + std::to_string(SPIRVIdx++) + ".spv");
+    if (Error E = writeFile(Filename, SPIRVBinary))
       return E;
-    outs() << "Extracted: " << Filename << "\n";
+    outs() << "Extracted SPIR-V: " << Filename << "\n";
   }
 
   return Error::success();
 }
 
+// Extract a regular (non-SPIR-V) binary image.
+static Error extractRegularBinary(const OffloadBinary *Binary,
+                                  StringRef InputFile, uint64_t Idx,
+                                  StringSaver &Saver) {
+  std::string Filename =
+      sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
+  StringRef Arch = Binary->getArch();
+  if (!Arch.empty())
+    Filename += "-" + Arch.str();
+  Filename += "." + std::to_string(Idx) + "." +
+              getImageKindName(Binary->getImageKind()).str();
+
+  if (Error E = writeFile(Saver.save(Filename), Binary->getImage()))
+    return E;
+  outs() << "Extracted: " << Filename << "\n";
+  return Error::success();
+}
+
+// Helper function to extract a single binary image, with SPIR-V support.
+// Returns Error on failure.
+static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
+                           uint64_t Idx, StringSaver &Saver) {
+  // Check if this is a SPIR-V image that needs special handling.
+  if (Binary->getTriple().starts_with("spirv64-intel"))
+    return extractSPIRVBinary(Binary, InputFile, Idx, Saver);
+  return extractRegularBinary(Binary, InputFile, Idx, Saver);
+}
+
 static Error unbundleImages() {
   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
       MemoryBuffer::getFileOrSTDIN(InputFile);

>From 95e00ba6c51d8957c4ac8cc068741bdaeea9530e Mon Sep 17 00:00:00 2001
From: "Duran, Alex" <alejandro.duran at intel.com>
Date: Mon, 9 Mar 2026 10:13:56 -0700
Subject: [PATCH 5/5] support OffloadBinary instead of special ELF

---
 .../docs/CommandGuide/llvm-offload-binary.rst |  21 +--
 .../llvm-offload-binary.cpp                   | 154 ++++++------------
 2 files changed, 59 insertions(+), 116 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-offload-binary.rst b/llvm/docs/CommandGuide/llvm-offload-binary.rst
index cee0f77ba8980..87005967775db 100644
--- a/llvm/docs/CommandGuide/llvm-offload-binary.rst
+++ b/llvm/docs/CommandGuide/llvm-offload-binary.rst
@@ -21,6 +21,11 @@ When extracting images, if no :option:`--image` filters are specified, all
 offload images are automatically extracted with descriptive filenames. When
 :option:`--image` filters are provided, only matching images are extracted.
 
+The tool supports nested OffloadBinary format, where device images can be wrapped
+in an inner OffloadBinary container. When extracting, the tool automatically
+detects and unwraps nested OffloadBinary images, making the format transparent
+to users.
+
 The binary format begins with the magic bytes ``0x10FF10AD``, followed by a
 version and size. Each binary contains its own header, allowing tools to locate
 offloading sections even when merged by a linker. Each offload entry includes
@@ -40,14 +45,12 @@ EXAMPLE
   $ llvm-offload-binary in.bin
   # Output:
   # Extracted: in-nvptx64-nvidia-cuda-sm_70.0.bc
-  # Extracted (ELF wrapper): in-spirv64-intel.0.elf
-  # Extracted SPIR-V: in-spirv64-intel.0_0.spv
+  # Extracted: in-spirv64-intel-unknown.0.spv
 
   # Extract only SPIR-V images using filters:
   $ llvm-offload-binary in.bin --image=triple=spirv64-intel
   # Output:
-  # Extracted (ELF wrapper): in-spirv64-intel.0.elf
-  # Extracted SPIR-V: in-spirv64-intel.0_0.spv
+  # Extracted: in-spirv64-intel-unknown.0.spv
 
   # Extract filtered images to a specific file:
   $ llvm-offload-binary in.bin --image=file=output.bc,arch=sm_70
@@ -214,8 +217,7 @@ Extract all embedded offload images to see what's inside:
   $ llvm-offload-binary myapp
   # Output:
   # Extracted: myapp-nvptx64-nvidia-cuda-sm_70.0.bc
-  # Extracted (ELF wrapper): myapp-spirv64-intel.1.elf
-  # Extracted SPIR-V: myapp-spirv64-intel.1_0.spv
+  # Extracted: myapp-spirv64-intel-unknown.1.spv
 
 **Workflow 2: Extract Specific Target**
 
@@ -225,8 +227,7 @@ Extract only images for a specific target:
 
   $ llvm-offload-binary myapp --image=triple=spirv64-intel
   # Output:
-  # Extracted (ELF wrapper): myapp-spirv64-intel.0.elf
-  # Extracted SPIR-V: myapp-spirv64-intel.0_0.spv
+  # Extracted: myapp-spirv64-intel-unknown.0.spv
 
 **Workflow 3: Create Device Image Archive**
 
@@ -245,8 +246,8 @@ Extract and validate SPIR-V binaries:
 .. code-block:: console
 
   $ llvm-offload-binary myapp --image=triple=spirv64-intel
-  $ spirv-val myapp-spirv64-intel.0_0.spv
-  $ spirv-dis myapp-spirv64-intel.0_0.spv -o kernel.spvasm
+  $ spirv-val myapp-spirv64-intel-unknown.0.spv
+  $ spirv-dis myapp-spirv64-intel-unknown.0.spv -o kernel.spvasm
 
 **Workflow 5: Bundle Multiple Targets**
 
diff --git a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
index a1e09d34f2836..8a06b5e2ce38f 100644
--- a/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
+++ b/llvm/tools/llvm-offload-binary/llvm-offload-binary.cpp
@@ -137,129 +137,66 @@ static Error bundleImages() {
   return Error::success();
 }
 
-// Extract SPIR-V binaries from an ELF image with triple "spirv64-intel" or
-// "spirv32-intel". These ELF images contain SPIR-V binaries in sections named
-// "__openmp_offload_spirv_*".
-static Expected<SmallVector<StringRef>>
-extractSPIRVFromELF(StringRef ImageData) {
-  SmallVector<StringRef> SPIRVBinaries;
-
-  // Try to parse as ELF object file.
-  Expected<std::unique_ptr<ObjectFile>> ObjOrErr =
-      ObjectFile::createObjectFile(MemoryBufferRef(ImageData, "spirv-elf"));
-  if (!ObjOrErr)
-    return ObjOrErr.takeError();
-
-  ObjectFile &Obj = *ObjOrErr->get();
-  if (!Obj.isELF())
-    return createStringError("expected ELF format for Intel SPIR-V image");
-
-  // Extract all sections with name matching "__openmp_offload_spirv_*".
-  for (const SectionRef &Sec : Obj.sections()) {
-    Expected<StringRef> NameOrErr = Sec.getName();
-    if (!NameOrErr) {
-      // Consume error and skip this section.
-      consumeError(NameOrErr.takeError());
-      continue;
-    }
-
-    if (!NameOrErr->starts_with("__openmp_offload_spirv_"))
-      continue;
-
-    Expected<StringRef> ContentsOrErr = Sec.getContents();
-    if (!ContentsOrErr)
-      return ContentsOrErr.takeError();
-
-    SPIRVBinaries.push_back(*ContentsOrErr);
+// Recursively unwrap nested OffloadBinaries to get the actual device image.
+static Expected<StringRef> unwrapImage(StringRef ImageData) {
+  // Check if the image contains a nested OffloadBinary.
+  if (identify_magic(ImageData) == file_magic::offload_binary) {
+    // Parse nested OffloadBinary.
+    MemoryBufferRef InnerBuffer(ImageData, "nested-offload-binary");
+    auto InnerBinaries = OffloadBinary::create(InnerBuffer);
+    if (!InnerBinaries)
+      return InnerBinaries.takeError();
+
+    // For single entry, recursively unwrap.
+    if (InnerBinaries->size() == 1)
+      return unwrapImage((*InnerBinaries)[0]->getImage());
+
+    // Multiple entries not supported for single file extraction.
+    return createStringError(inconvertibleErrorCode(),
+                             "nested OffloadBinary contains multiple entries");
   }
 
-  if (SPIRVBinaries.empty())
-    return createStringError("no SPIR-V sections found in ELF image");
-
-  return SPIRVBinaries;
+  // Base case: return the actual device image.
+  return ImageData;
 }
 
-// Extract a SPIR-V binary image (spirv64-intel or spirv32-intel).
-static Error extractSPIRVBinary(const OffloadBinary *Binary,
-                                StringRef InputFile, uint64_t Idx,
-                                StringSaver &Saver) {
+// Extract a single OffloadBinary, recursively handling nested OffloadBinaries.
+static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
+                           uint64_t &Idx, StringSaver &Saver) {
   StringRef ImageData = Binary->getImage();
-  std::string BaseFilename =
-      sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
-  StringRef Arch = Binary->getArch();
-  if (!Arch.empty())
-    BaseFilename += "-" + Arch.str();
-  BaseFilename += "." + std::to_string(Idx);
-
-  // Check if the image is already raw SPIR-V (not ELF-wrapped).
-  if (identify_magic(ImageData) == file_magic::spirv_object) {
-    // Image is already SPIR-V, just extract it with .spv extension.
-    StringRef Filename = Saver.save(BaseFilename + ".spv");
-    if (Error E = writeFile(Filename, ImageData))
-      return E;
-    outs() << "Extracted SPIR-V: " << Filename << "\n";
-    return Error::success();
-  }
 
-  // Try to parse as ELF and extract SPIR-V from sections.
-  auto SPIRVBinariesOrErr = extractSPIRVFromELF(ImageData);
-  if (!SPIRVBinariesOrErr) {
-    // Not ELF or no SPIR-V sections, extract as-is with .bin extension.
-    StringRef Filename = Saver.save(BaseFilename + ".bin");
-    if (Error E = writeFile(Filename, ImageData))
-      return E;
-    outs() << "Extracted (unknown format): " << Filename << "\n";
+  // Check if the image contains a nested OffloadBinary.
+  if (identify_magic(ImageData) == file_magic::offload_binary) {
+    // Parse nested OffloadBinary.
+    MemoryBufferRef InnerBuffer(ImageData, "nested-offload-binary");
+    auto InnerBinaries = OffloadBinary::create(InnerBuffer);
+    if (!InnerBinaries)
+      return InnerBinaries.takeError();
+
+    // Recursively extract each nested binary.
+    for (const auto &InnerBinary : *InnerBinaries) {
+      if (Error E = extractBinary(InnerBinary.get(), InputFile, Idx, Saver))
+        return E;
+    }
     return Error::success();
   }
 
-  // Successfully extracted SPIR-V from ELF.
-  // Extract the ELF wrapper.
-  StringRef ELFFilename = Saver.save(BaseFilename + ".elf");
-  if (Error E = writeFile(ELFFilename, ImageData))
-    return E;
-  outs() << "Extracted (ELF wrapper): " << ELFFilename << "\n";
-
-  // Extract each SPIR-V binary found in the ELF.
-  uint64_t SPIRVIdx = 0;
-  for (StringRef SPIRVBinary : *SPIRVBinariesOrErr) {
-    StringRef Filename =
-        Saver.save(BaseFilename + "_" + std::to_string(SPIRVIdx++) + ".spv");
-    if (Error E = writeFile(Filename, SPIRVBinary))
-      return E;
-    outs() << "Extracted SPIR-V: " << Filename << "\n";
-  }
-
-  return Error::success();
-}
-
-// Extract a regular (non-SPIR-V) binary image.
-static Error extractRegularBinary(const OffloadBinary *Binary,
-                                  StringRef InputFile, uint64_t Idx,
-                                  StringSaver &Saver) {
+  // Base case: extract the actual device image.
   std::string Filename =
       sys::path::stem(InputFile).str() + "-" + Binary->getTriple().str();
   StringRef Arch = Binary->getArch();
   if (!Arch.empty())
     Filename += "-" + Arch.str();
-  Filename += "." + std::to_string(Idx) + "." +
+  Filename += "." + std::to_string(Idx++) + "." +
               getImageKindName(Binary->getImageKind()).str();
 
-  if (Error E = writeFile(Saver.save(Filename), Binary->getImage()))
+  if (Error E = writeFile(Saver.save(Filename), ImageData))
     return E;
+
   outs() << "Extracted: " << Filename << "\n";
   return Error::success();
 }
 
-// Helper function to extract a single binary image, with SPIR-V support.
-// Returns Error on failure.
-static Error extractBinary(const OffloadBinary *Binary, StringRef InputFile,
-                           uint64_t Idx, StringSaver &Saver) {
-  // Check if this is a SPIR-V image that needs special handling.
-  if (Binary->getTriple().starts_with("spirv64-intel"))
-    return extractSPIRVBinary(Binary, InputFile, Idx, Saver);
-  return extractRegularBinary(Binary, InputFile, Idx, Saver);
-}
-
 static Error unbundleImages() {
   ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
       MemoryBuffer::getFileOrSTDIN(InputFile);
@@ -277,13 +214,13 @@ static Error unbundleImages() {
   if (Error Err = extractOffloadBinaries(*Buffer, Binaries))
     return Err;
 
-  // If no filters specified, extract all images
+  // If no filters specified, extract all images.
   if (DeviceImages.empty()) {
     BumpPtrAllocator Alloc;
     StringSaver Saver(Alloc);
     uint64_t Idx = 0;
     for (const OffloadFile &File : Binaries) {
-      if (Error E = extractBinary(File.getBinary(), InputFile, Idx++, Saver))
+      if (Error E = extractBinary(File.getBinary(), InputFile, Idx, Saver))
         return E;
     }
     return Error::success();
@@ -334,12 +271,17 @@ static Error unbundleImages() {
         WithColor::warning(errs(), PackagerExecutable)
             << "Multiple inputs match to a single file, '" << It->second
             << "'\n";
-      if (Error E = writeFile(It->second, Extracted.back()->getImage()))
+      const OffloadBinary *Binary = Extracted.back();
+      // Recursively unwrap any nested OffloadBinaries.
+      auto ImageOrErr = unwrapImage(Binary->getImage());
+      if (!ImageOrErr)
+        return ImageOrErr.takeError();
+      if (Error E = writeFile(It->second, *ImageOrErr))
         return E;
     } else {
       uint64_t Idx = 0;
       for (const OffloadBinary *Binary : Extracted) {
-        if (Error E = extractBinary(Binary, InputFile, Idx++, Saver))
+        if (Error E = extractBinary(Binary, InputFile, Idx, Saver))
           return E;
       }
     }



More information about the llvm-commits mailing list