[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
Thu Mar 5 03:34:15 PST 2026
https://github.com/adurang created https://github.com/llvm/llvm-project/pull/184774
Enhance the llvm-offload-binary tool to be able to unbudle with logic to handle different cases related to spirv64-intel offload binary images.
It also allows to extract all images without requiring the use --image options to simplify its use.
The base of this PR was generated with Claude which I reviewed, tested and adjusted.
This PR supersedes previous approaches (#181689 and #180715) as I think it fits better in our current toolchain.
>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] [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;
}
}
More information about the llvm-commits
mailing list