Struggling with simple mlir-translate to spir-v

I want to write spir-v fragment shaders with MLIR but I’m struggling to get a very simple mlir to translate to spir-v

I tried translating this:

Failed:

➜  mlir-playground git:(main) ✗  mlir-translate -no-implicit-module -split-input-file -serialize-spirv -deserialize-spirv hello.mlir 
mlir-translate: Unknown command line argument '-no-implicit-module'.  Try: 'mlir-translate --help'
mlir-translate: Did you mean '--arm-implicit-it'?

➜  llvm-project git:(main) mlir-translate -serialize-spirv mlir/test/Target/SPIRV/array-two-step-roundtrip.mlir           
mlir/test/Target/SPIRV/array-two-step-roundtrip.mlir:3:1: error: custom op 'spirv.module' is unknown
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader], []> {
^
➜  llvm-project git:(main) mlir-translate -deserialize-spirv mlir/test/Target/SPIRV/array-two-step-roundtrip.mlir
<unknown>:0: error: SPIR-V binary module must contain integral number of 32-bit words

Related: SPIR-V to SPIR-V dialect translation - #2 by antiagainst

Which version of the code did you build? The Unknown command line argument '-no-implicit-module' Makes me think you’re trying a newer example with an old build, either build at HEAD or look for an example in the repo at the same revision as your build.

Note that there are two examples in this file, and the --split-input-file will process them by splitting on // -----, this is fine because the test does serialization and deserialization. Otherwise the result may not be a valid spirv module (it’ll output the two in a two).

Ah I think the version was the issue, thank you!

My Brew installed LLVM: v15
Source code: main(v16-ish)

I built the latest branch and used the new binaries which works fine but I get a new error when I tried to convert the binary to text format.

llvm-project git:(main) ✗ mlir-translate -no-implicit-module -split-input-file -serialize-spirv mlir/test/Target/SPIRV/arithmetic-ops.mlir >> x.module
➜  llvm-project git:(main) ✗ spirv-dis x.module
; SPIR-V
; Version: 1.0
; Generator: Khronos; 22
; Bound: 16
; Schema: 0
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpName %array_stride "array_stride"
               OpDecorate %_arr_float_uint_4 ArrayStride 4
               OpDecorate %_arr__arr_float_uint_4_uint_4 ArrayStride 128
       %void = OpTypeVoid
      %float = OpTypeFloat 32
       %uint = OpTypeInt 32 0
     %uint_4 = OpConstant %uint 4
%_arr_float_uint_4 = OpTypeArray %float %uint_4
%_arr__arr_float_uint_4_uint_4 = OpTypeArray %_arr_float_uint_4 %uint_4
%_ptr_StorageBuffer__arr__arr_float_uint_4_uint_4 = OpTypePointer StorageBuffer %_arr__arr_float_uint_4_uint_4
          %1 = OpTypeFunction %void %_ptr_StorageBuffer__arr__arr_float_uint_4_uint_4 %uint %uint
%_ptr_StorageBuffer_float = OpTypePointer StorageBuffer %float
%array_stride = OpFunction %void None %1
         %10 = OpFunctionParameter %_ptr_StorageBuffer__arr__arr_float_uint_4_uint_4
         %11 = OpFunctionParameter %uint
         %12 = OpFunctionParameter %uint
         %13 = OpLabel
         %15 = OpAccessChain %_ptr_StorageBuffer_float %10 %11 %12
               OpReturn
               OpFunctionEnd
error: 23: Invalid opcode: 515

Here’s the version of spirv-dis

llvm-project git:(main) ✗ spirv-dis --version
SPIRV-Tools v2023.1 unknown hash, 2023-01-18T14:55:50
Target: SPIR-V 1.5

mlir-translate seems to be happy to deserialize for me, I can’t tell if we generate invalid bytecode or if there is an issue with the spirv-dis tool here. @antiagainst or @kuhar may be able to help?

1 Like

I think I’ve got it working fine.
The issue is because it has two modules in a single file.
I deleted one of the modules and it was working fine.

mlir-translate -no-implicit-module -serialize-spirv mlir/test/Target/SPIRV/arithmetic-ops.mlir | spirv-dis

Thank you!

Ah, I warned you about this earlier :wink:

1 Like

Thanks Mehdi for resolving the issues!

I deleted one of the modules and it was working fine.

Yes the serializer and deserializer is meant to work on only a single spirv.module op.

I noticed that you mentioned you are playing with fragment shaders, great! But keep in mind that right now we are mainly using MLIR SPIR-V code generation for compute use cases (Vulkan compute, OpenCL); graphics support is not complete. You will likely hit other issues. If that happens, don’t hesitate to reach out!

1 Like

Ah, I warned you about this earlier :wink:

Haha yes I’m so sorry :laughing:

But keep in mind that right now we are mainly using MLIR SPIR-V code generation for compute use cases

Huh that’s good to know.
I’m actually interested in if I can convert mlir → vulkan → glsl and run in webgl so I’d not be surprised if I just give up midway through haha

You will likely hit other issues. If that happens, don’t hesitate to reach out!

Thank you so much. I want to do mlir → spirv → glsl
But in order to get the glsl I want, I tried to do the reverse(glsl → spirv → mlir) with a very simple shader to get an understanding of what I should be doing.

frag.frag

#version 460
out vec4 FragColor;
in vec3 ourColor;

void main()
{
    FragColor = vec4(ourColor, 1.0);
}

frag.spv(compiled glsl with naga and disassembled with spirv-dis)

; SPIR-V
; Version: 1.0
; Generator: Khronos; 28
; Bound: 35
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %29 "main" %24 %27
               OpExecutionMode %29 OriginUpperLeft
               OpMemberDecorate %_struct_7 0 Offset 0
               OpDecorate %24 Location 0
               OpDecorate %27 Location 0
       %void = OpTypeVoid
      %float = OpTypeFloat 32
    %float_1 = OpConstant %float 1
    %v4float = OpTypeVector %float 4
    %v3float = OpTypeVector %float 3
  %_struct_7 = OpTypeStruct %v4float
%_ptr_Private_v4float = OpTypePointer Private %v4float
         %10 = OpConstantNull %v4float
          %8 = OpVariable %_ptr_Private_v4float Private %10
%_ptr_Private_v3float = OpTypePointer Private %v3float
         %13 = OpConstantNull %v3float
         %11 = OpVariable %_ptr_Private_v3float Private %13
         %16 = OpTypeFunction %void
%_ptr_Input_v3float = OpTypePointer Input %v3float
         %24 = OpVariable %_ptr_Input_v3float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
         %27 = OpVariable %_ptr_Output_v4float Output
         %15 = OpFunction %void None %16
         %14 = OpLabel
               OpBranch %17
         %17 = OpLabel
         %18 = OpLoad %v3float %11
         %19 = OpCompositeExtract %float %18 0
         %20 = OpCompositeExtract %float %18 1
         %21 = OpCompositeExtract %float %18 2
         %22 = OpCompositeConstruct %v4float %19 %20 %21 %float_1
               OpStore %8 %22
               OpReturn
               OpFunctionEnd
         %29 = OpFunction %void None %16
         %23 = OpLabel
         %26 = OpLoad %v3float %24
               OpBranch %30
         %30 = OpLabel
               OpStore %11 %26
         %31 = OpFunctionCall %void %15
         %32 = OpLoad %v4float %8
         %33 = OpCompositeConstruct %_struct_7 %32
         %34 = OpCompositeExtract %v4float %33 0
               OpStore %27 %34
               OpReturn
               OpFunctionEnd

I got this error.

 ✗ mlir-translate -no-implicit-module -deserialize-spirv frag.spv
<unknown>:0: error: unknown <id> 10used as initializer

What might be the issue? I can’t tell from this error…

While I’m not sure exactly what above issue is, I guess it’s related null-support in glsl.

Since reversing(glsl → spirv → mlir) seemed difficult, I read through spirv dialect documentation to write below mlir. This compiled successfully to glsl with mlir-translate & spirv-cross.

simpleFrag.mlir

spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader], []> {
  spirv.GlobalVariable @in {location = 0 : i32} : !spirv.ptr<vector<3xf32>, Input>
  spirv.GlobalVariable @out {location = 0 : i32} : !spirv.ptr<vector<4xf32>, Output>

  spirv.func @vec3ToVec4(%arg1 : vector<3xf32>) -> vector<4xf32> "None" {
    %int_1 = spirv.Constant 1.0: f32
    %1 = spirv.CompositeExtract %arg1[0 : i32] :vector<3xf32>
    %2 = spirv.CompositeExtract %arg1[1 : i32] :vector<3xf32>
    %3 = spirv.CompositeExtract %arg1[2: i32] :vector<3xf32>
    %4 = spirv.CompositeConstruct %1, %2, %3, %int_1 : (f32,f32,f32,f32) -> vector<4xf32>
    spirv.ReturnValue %4 : vector<4xf32>
  }

  spirv.func @main() -> () "None" {
    %in_ptr = spirv.mlir.addressof @in : !spirv.ptr<vector<3xf32>, Input>
    %out_ptr = spirv.mlir.addressof @out : !spirv.ptr<vector<4xf32>, Output>
    %in = spirv.Load "Input" %in_ptr : vector<3xf32>

    %vec4 = spirv.FunctionCall @vec3ToVec4(%in) : (vector<3xf32>) ->  (vector<4xf32>)
    spirv.Store "Output" %out_ptr, %vec4 : vector<4xf32>

    spirv.Return
  }
  spirv.EntryPoint "Fragment" @main
}

compiling:

➜  mlir-playground git:(main) ✗ mlir-translate -no-implicit-module -serialize-spirv simpleFrag.mlir | spirv-cross -
#version 450

layout(location = 0) in vec3 _in;
layout(location = 0) out vec4 _out;

vec4 vec3ToVec4(vec3 _10)
{
    return vec4(_10, 1.0);
}

void main()
{
    _out = vec3ToVec4(_in);
}

I think I’ve got a very rough understanding of mlir, so thank you for your support and good documentation.

With that said, I will leave some opinions on the doc for future new-comers.

  1. mlir-opt and mlir-translate should be the first thing to introduce to new-comers. While I’m unfamiliar with any compiler IRs, coding a .mlir and mlir-translate gave me a very good idea of what mlir is capable of. (Related: [doc] mlir-translate / mlir-opt - #30 by clattner)
  2. The relationship between various dialects are obscure. I wish a comprehensive directed graph diagram of dialects like this one was available at the root of dialect documentation.

Anyways, thank you!

Btw I feel like composite construct example is wrong.

My suggestion:

-    %0 = spirv.CompositeConstruct %1, %2, %3 : vector<3xf32>
+    %0 = spirv.CompositeConstruct %1, %2, %3 : (f32,f32,f32) -> vector<3xf32>

This actually hits a not-yet-implemented feature in deserializer–it supports other global variables as initializer but not yet constants (which comes from OpConstantNull). Sorry about the confusion.

Note that the deserialization logic is coupled with the serialization logic. Right now we mostly developed it as a way to test the serialization logic in round-trip tests. So it only covers the SPIR-V cases we commonly see in compute cases for ML. In reality, to have a robust deserializer, we need to handle way more complicated SPIR-V cases in the wild (because there are many SPIR-V compilers/producers and can generate SPIR-V in different forms). This part certainly needs more investment. (This is also what I meant previously by saying "You will likely hit other issues.)

1 Like

Yup, going from MLIR SPIR-V dialect to SPIR-V blob is certainly more battle tested and well supported. :slight_smile:

Ah, yes. The assembly form of spirv.CompositeConstruct changed a while ago but we forgot to update the example there. Please feel free to send a patch to have it fixed! :slight_smile:

1 Like