Skip to content

Commit 0f346a4

Browse files
authored
[mlir][spirv] Enable serializer to write SPIR-V modules into separate files (llvm#152678)
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: output is written to a single `os` that is passed around, and the 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.
1 parent ba5ff57 commit 0f346a4

File tree

3 files changed

+106
-10
lines changed

3 files changed

+106
-10
lines changed

mlir/include/mlir/Target/SPIRV/Serialization.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#include "mlir/Support/LLVM.h"
1717
#include <cstdint>
18+
#include <string>
1819

1920
namespace mlir {
2021
class MLIRContext;
@@ -27,12 +28,33 @@ struct SerializationOptions {
2728
bool emitSymbolName = true;
2829
/// Whether to emit `OpLine` location information for SPIR-V ops.
2930
bool emitDebugInfo = false;
31+
/// Whether to store a module to an additional file during
32+
/// serialization. This is used to store the SPIR-V module to the
33+
/// file in addition to writing it to `os` passed from the calling
34+
/// tool. This saved file is later used for validation.
35+
bool saveModuleForValidation = false;
36+
/// A prefix prepended to the file used when `saveModuleForValidation`
37+
/// is set to `true`. This can either be a file prefix, or a relative or
38+
/// or an absolute path followed by the prefix. For example:
39+
///
40+
/// * "foo" - Create files with a `foo` prefix in the current working
41+
/// directory. For example: `fooXYZ123`, `fooABC456` ... `fooXXXXXX`.
42+
/// The last 6 characters will be a unique combination as
43+
/// generated by `llvm::sys::fs::createUniqueFile`.
44+
///
45+
/// * "my/dir/foo" - Create files in `my/dir` with a `foo` prefix. The
46+
/// `my/dir` need to exists. For example: `fooXYZ123`, `fooABC456` ...
47+
/// `fooXXXXXX` will be created and stored in `/my/dir`. Filenames
48+
/// follow the same pattern as above.
49+
///
50+
/// * "/home/user/my/dir" - Same as above but using an absolute path.
51+
std::string validationFilePrefix = "";
3052
};
3153

32-
/// Serializes the given SPIR-V `module` and writes to `binary`. On failure,
54+
/// Serializes the given SPIR-V `moduleOp` and writes to `binary`. On failure,
3355
/// reports errors to the error handler registered with the MLIR context for
34-
/// `module`.
35-
LogicalResult serialize(ModuleOp module, SmallVectorImpl<uint32_t> &binary,
56+
/// `moduleOp`.
57+
LogicalResult serialize(ModuleOp moduleOp, SmallVectorImpl<uint32_t> &binary,
3658
const SerializationOptions &options = {});
3759

3860
} // namespace spirv

mlir/lib/Target/SPIRV/TranslateRegistration.cpp

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
#include "mlir/Target/SPIRV/Serialization.h"
2222
#include "mlir/Tools/mlir-translate/Translation.h"
2323
#include "llvm/ADT/StringRef.h"
24+
#include "llvm/Support/FileSystem.h"
2425
#include "llvm/Support/MemoryBuffer.h"
26+
#include "llvm/Support/Path.h"
2527
#include "llvm/Support/SourceMgr.h"
28+
#include "llvm/Support/ToolOutputFile.h"
2629

2730
using namespace mlir;
2831

@@ -76,24 +79,66 @@ void registerFromSPIRVTranslation() {
7679
// Serialization registration
7780
//===----------------------------------------------------------------------===//
7881

79-
static LogicalResult serializeModule(spirv::ModuleOp module,
80-
raw_ostream &output) {
82+
static LogicalResult
83+
serializeModule(spirv::ModuleOp moduleOp, raw_ostream &output,
84+
const spirv::SerializationOptions &options) {
8185
SmallVector<uint32_t, 0> binary;
82-
if (failed(spirv::serialize(module, binary)))
86+
if (failed(spirv::serialize(moduleOp, binary)))
8387
return failure();
8488

85-
output.write(reinterpret_cast<char *>(binary.data()),
86-
binary.size() * sizeof(uint32_t));
89+
size_t sizeInBytes = binary.size() * sizeof(uint32_t);
90+
91+
output.write(reinterpret_cast<char *>(binary.data()), sizeInBytes);
92+
93+
if (options.saveModuleForValidation) {
94+
size_t dirSeparator =
95+
options.validationFilePrefix.find(llvm::sys::path::get_separator());
96+
// If file prefix includes directory check if that directory exists.
97+
if (dirSeparator != std::string::npos) {
98+
llvm::StringRef parentDir =
99+
llvm::sys::path::parent_path(options.validationFilePrefix);
100+
if (!llvm::sys::fs::is_directory(parentDir))
101+
return moduleOp.emitError(
102+
"validation prefix directory does not exist\n");
103+
}
104+
105+
SmallString<128> filename;
106+
int fd = 0;
107+
108+
std::error_code errorCode = llvm::sys::fs::createUniqueFile(
109+
options.validationFilePrefix + "%%%%%%", fd, filename);
110+
if (errorCode)
111+
return moduleOp.emitError("error creating validation output file: ")
112+
<< errorCode.message() << "\n";
113+
114+
llvm::raw_fd_ostream validationOutput(fd, /*shouldClose=*/true);
115+
validationOutput.write(reinterpret_cast<char *>(binary.data()),
116+
sizeInBytes);
117+
validationOutput.flush();
118+
}
87119

88120
return mlir::success();
89121
}
90122

91123
namespace mlir {
92124
void registerToSPIRVTranslation() {
125+
static llvm::cl::opt<std::string> validationFilesPrefix(
126+
"spirv-save-validation-files-with-prefix",
127+
llvm::cl::desc(
128+
"When non-empty string is passed each serialized SPIR-V module is "
129+
"saved to an additional file that starts with the given prefix. This "
130+
"is used to generate separate binaries for validation, where "
131+
"`--split-input-file` normally combines all outputs into one. The "
132+
"one combined output (`-o`) is still written. Created files need to "
133+
"be removed manually once processed."),
134+
llvm::cl::init(""));
135+
93136
TranslateFromMLIRRegistration toBinary(
94137
"serialize-spirv", "serialize SPIR-V dialect",
95-
[](spirv::ModuleOp module, raw_ostream &output) {
96-
return serializeModule(module, output);
138+
[](spirv::ModuleOp moduleOp, raw_ostream &output) {
139+
return serializeModule(moduleOp, output,
140+
{true, false, !validationFilesPrefix.empty(),
141+
validationFilesPrefix});
97142
},
98143
[](DialectRegistry &registry) {
99144
registry.insert<spirv::SPIRVDialect>();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Check that `--spirv-save-validation-files-with-prefix` generates
2+
// a correct number of files.
3+
4+
// REQUIRES: shell
5+
// RUN: rm -rf %t
6+
// RUN: mkdir %t && mlir-translate --serialize-spirv --no-implicit-module \
7+
// RUN: --split-input-file --spirv-save-validation-files-with-prefix=%t/foo %s \
8+
// RUN: && ls %t | wc -l | FileCheck %s
9+
// RUN: rm -rf %t
10+
11+
// CHECK: 4
12+
13+
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
14+
}
15+
16+
// -----
17+
18+
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
19+
}
20+
21+
// -----
22+
23+
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
24+
}
25+
26+
// -----
27+
28+
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
29+
}

0 commit comments

Comments
 (0)