[Mlir-commits] [mlir] [mlir][spirv] Enable serializer to write SPIR-V modules into separate files (PR #152678)

Igor Wodiany llvmlistbot at llvm.org
Mon Aug 11 03:42:56 PDT 2025


https://github.com/IgWod-IMG updated https://github.com/llvm/llvm-project/pull/152678

>From d82720fe4f511033b17c836ed54bac18818cbe1e Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at imgtec.com>
Date: Thu, 7 Aug 2025 15:31:04 +0000
Subject: [PATCH 1/4] [mlir][spirv] Enable serializer to write SPIR-V modules
 into separate files

By default, `mlir-translate` writes all output into a single file even
when `--split-input-file` is used. This is not an issue for text files
as they can be easily split with an output separator. However, this
causes issues with binary SPIR-V modules.

Firstly, a binary file with multiple modules is not a valid SPIR-V,
but will be created if multiple modules are specified in the same
file and separated by "// -----". This does not cause issues with
MLIR internal tools but does not work with SPIRV-Tools.

Secondly, splitting binary files after serialization is non-trivial,
when compared to text files, so using an external tool is not
desirable.

This patch adds a SPIR-V serialization option that write SPIR-V modules
to separate files in addition to writing them to the `mlir-translate`
output file. This is not the ideal solution and ideally `mlir-translate`
would allow generating multiple output files when `--split-input-file`
is used, however adding such functionality is again non-trival due to
how processing and splitting is done: function handles writing to
a single `os` that are passed around, and number of split buffers is not
known ahead of time. As such a I propose to have a SPIR-V internal option
that will dump modules to files in the form they can be processed by
`spirv-val`. The behaviour of the new added argument may be confusing,
but benefits from being internal to SPIR-V target.

Alternatively, we could expose the spirv option in `mlir/lib/Tools/mlir-translate/MlirTranslateMain.cpp`,
and slice the output file on the SPIR-V magic number, and not keep
the file generated by default by `mlir-translate`. This would be a bit
cleaner in API sense, as it would not generate the additional file
containing all modules together. However, it pushes SPIR-V specific
code into the generic part of the `mlir-translate` and slicing is
potentially more error prone that just writing a single module
after it was serialized.
---
 .../include/mlir/Target/SPIRV/Serialization.h |  8 ++++
 .../Target/SPIRV/TranslateRegistration.cpp    | 44 ++++++++++++++++---
 2 files changed, 47 insertions(+), 5 deletions(-)

diff --git a/mlir/include/mlir/Target/SPIRV/Serialization.h b/mlir/include/mlir/Target/SPIRV/Serialization.h
index 225777e25d607..feb80b7d0970e 100644
--- a/mlir/include/mlir/Target/SPIRV/Serialization.h
+++ b/mlir/include/mlir/Target/SPIRV/Serialization.h
@@ -27,6 +27,14 @@ struct SerializationOptions {
   bool emitSymbolName = true;
   /// Whether to emit `OpLine` location information for SPIR-V ops.
   bool emitDebugInfo = false;
+  /// Whether to store a module to an additional file during
+  /// serialization. This is used to store the SPIR-V module to the
+  /// file in addition to writing it to `os` passed from the calling
+  /// tool. This saved file is later used for validation.
+  bool saveModuleForValidation = false;
+  /// A prefix prepended to the file used when `saveModuleForValidation`
+  /// is set to `true`.
+  std::string validationFilePrefix = "";
 };
 
 /// Serializes the given SPIR-V `module` and writes to `binary`. On failure,
diff --git a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
index ac338d555e320..5272c63db6831 100644
--- a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
+++ b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
@@ -23,6 +23,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/ToolOutputFile.h"
 
 using namespace mlir;
 
@@ -76,24 +77,57 @@ void registerFromSPIRVTranslation() {
 // Serialization registration
 //===----------------------------------------------------------------------===//
 
-static LogicalResult serializeModule(spirv::ModuleOp module,
-                                     raw_ostream &output) {
+// Static variable is probably not ideal, but it lets us have unique files names
+// without taking additional parameters from `mlir-translate`.
+static size_t validationFileCounter = 0;
+
+static LogicalResult
+serializeModule(spirv::ModuleOp module, raw_ostream &output,
+                const spirv::SerializationOptions &options) {
+
   SmallVector<uint32_t, 0> binary;
   if (failed(spirv::serialize(module, binary)))
     return failure();
 
-  output.write(reinterpret_cast<char *>(binary.data()),
-               binary.size() * sizeof(uint32_t));
+  size_t sizeInBytes = binary.size() * sizeof(uint32_t);
+
+  output.write(reinterpret_cast<char *>(binary.data()), sizeInBytes);
+
+  if (options.saveModuleForValidation) {
+    std::string errorMessage;
+    std::string filename =
+        options.validationFilePrefix + std::to_string(validationFileCounter++);
+    auto validationOutput = openOutputFile(filename, &errorMessage);
+    if (!validationOutput) {
+      llvm::errs() << errorMessage << "\n";
+      return failure();
+    }
+    validationOutput->os().write(reinterpret_cast<char *>(binary.data()),
+                                 sizeInBytes);
+    validationOutput->keep();
+  }
 
   return mlir::success();
 }
 
 namespace mlir {
 void registerToSPIRVTranslation() {
+  static llvm::cl::opt<std::string> validationFilesPrefix(
+      "spirv-save-validation-files-with-prefix",
+      llvm::cl::desc(
+          "When non-empty string is passed each serialized SPIR-V module is "
+          "saved to an additional file that starts with the given prefix. This "
+          "is used to generate separate binaries for validation, where "
+          "`--split-input-file` normally combines all outputs into one. The "
+          "one combined output (`-o`) is still written."),
+      llvm::cl::init(""));
+
   TranslateFromMLIRRegistration toBinary(
       "serialize-spirv", "serialize SPIR-V dialect",
       [](spirv::ModuleOp module, raw_ostream &output) {
-        return serializeModule(module, output);
+        return serializeModule(module, output,
+                               {true, false, (validationFilesPrefix != ""),
+                                validationFilesPrefix});
       },
       [](DialectRegistry &registry) {
         registry.insert<spirv::SPIRVDialect>();

>From cbfbd1b030277c716d958898ffd750608c02e9e2 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at imgtec.com>
Date: Fri, 8 Aug 2025 09:49:15 +0000
Subject: [PATCH 2/4] missing header for mlir-capi-execution-engine-test

---
 mlir/include/mlir/Target/SPIRV/Serialization.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/mlir/include/mlir/Target/SPIRV/Serialization.h b/mlir/include/mlir/Target/SPIRV/Serialization.h
index feb80b7d0970e..e5d6859564d1e 100644
--- a/mlir/include/mlir/Target/SPIRV/Serialization.h
+++ b/mlir/include/mlir/Target/SPIRV/Serialization.h
@@ -15,6 +15,7 @@
 
 #include "mlir/Support/LLVM.h"
 #include <cstdint>
+#include <string>
 
 namespace mlir {
 class MLIRContext;

>From c2bda5a3bd2175033e7279d1fc228e7999895932 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at imgtec.com>
Date: Fri, 8 Aug 2025 14:45:55 +0000
Subject: [PATCH 3/4] Check if prefix dir exists

---
 mlir/lib/Target/SPIRV/TranslateRegistration.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
index 5272c63db6831..ac3ebfdeb23e9 100644
--- a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
+++ b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
@@ -21,7 +21,9 @@
 #include "mlir/Target/SPIRV/Serialization.h"
 #include "mlir/Tools/mlir-translate/Translation.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/ToolOutputFile.h"
 
@@ -94,6 +96,17 @@ serializeModule(spirv::ModuleOp module, raw_ostream &output,
   output.write(reinterpret_cast<char *>(binary.data()), sizeInBytes);
 
   if (options.saveModuleForValidation) {
+    size_t dirSeparator =
+        options.validationFilePrefix.find(llvm::sys::path::get_separator());
+    // If file prefix includes directory check if that directory exists.
+    if (dirSeparator != std::string::npos) {
+      llvm::StringRef parentDir =
+          llvm::sys::path::parent_path(options.validationFilePrefix);
+      if (!llvm::sys::fs::is_directory(parentDir)) {
+        llvm::errs() << "validation prefix directory does not exist\n";
+        return failure();
+      }
+    }
     std::string errorMessage;
     std::string filename =
         options.validationFilePrefix + std::to_string(validationFileCounter++);

>From 625308de3fa9180763ed8d470b89cbbe9970fa28 Mon Sep 17 00:00:00 2001
From: Igor Wodiany <igor.wodiany at imgtec.com>
Date: Mon, 11 Aug 2025 10:20:24 +0000
Subject: [PATCH 4/4] Address minor comments

---
 mlir/include/mlir/Target/SPIRV/Serialization.h |  6 +++---
 .../lib/Target/SPIRV/TranslateRegistration.cpp | 18 ++++++++++--------
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/mlir/include/mlir/Target/SPIRV/Serialization.h b/mlir/include/mlir/Target/SPIRV/Serialization.h
index e5d6859564d1e..67de6ccef487b 100644
--- a/mlir/include/mlir/Target/SPIRV/Serialization.h
+++ b/mlir/include/mlir/Target/SPIRV/Serialization.h
@@ -38,10 +38,10 @@ struct SerializationOptions {
   std::string validationFilePrefix = "";
 };
 
-/// Serializes the given SPIR-V `module` and writes to `binary`. On failure,
+/// Serializes the given SPIR-V `moduleOp` and writes to `binary`. On failure,
 /// reports errors to the error handler registered with the MLIR context for
-/// `module`.
-LogicalResult serialize(ModuleOp module, SmallVectorImpl<uint32_t> &binary,
+/// `moduleOp`.
+LogicalResult serialize(ModuleOp moduleOp, SmallVectorImpl<uint32_t> &binary,
                         const SerializationOptions &options = {});
 
 } // namespace spirv
diff --git a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
index ac3ebfdeb23e9..8877461867bb3 100644
--- a/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
+++ b/mlir/lib/Target/SPIRV/TranslateRegistration.cpp
@@ -84,11 +84,10 @@ void registerFromSPIRVTranslation() {
 static size_t validationFileCounter = 0;
 
 static LogicalResult
-serializeModule(spirv::ModuleOp module, raw_ostream &output,
+serializeModule(spirv::ModuleOp moduleOp, raw_ostream &output,
                 const spirv::SerializationOptions &options) {
-
   SmallVector<uint32_t, 0> binary;
-  if (failed(spirv::serialize(module, binary)))
+  if (failed(spirv::serialize(moduleOp, binary)))
     return failure();
 
   size_t sizeInBytes = binary.size() * sizeof(uint32_t);
@@ -110,7 +109,8 @@ serializeModule(spirv::ModuleOp module, raw_ostream &output,
     std::string errorMessage;
     std::string filename =
         options.validationFilePrefix + std::to_string(validationFileCounter++);
-    auto validationOutput = openOutputFile(filename, &errorMessage);
+    std::unique_ptr<llvm::ToolOutputFile> validationOutput =
+        openOutputFile(filename, &errorMessage);
     if (!validationOutput) {
       llvm::errs() << errorMessage << "\n";
       return failure();
@@ -132,14 +132,16 @@ void registerToSPIRVTranslation() {
           "saved to an additional file that starts with the given prefix. This "
           "is used to generate separate binaries for validation, where "
           "`--split-input-file` normally combines all outputs into one. The "
-          "one combined output (`-o`) is still written."),
+          "one combined output (`-o`) is still written. Created files need to "
+          "be "
+          "removed manually once processed."),
       llvm::cl::init(""));
 
   TranslateFromMLIRRegistration toBinary(
       "serialize-spirv", "serialize SPIR-V dialect",
-      [](spirv::ModuleOp module, raw_ostream &output) {
-        return serializeModule(module, output,
-                               {true, false, (validationFilesPrefix != ""),
+      [](spirv::ModuleOp moduleOp, raw_ostream &output) {
+        return serializeModule(moduleOp, output,
+                               {true, false, !validationFilesPrefix.empty(),
                                 validationFilesPrefix});
       },
       [](DialectRegistry &registry) {



More information about the Mlir-commits mailing list