Sunday, May 24, 2015

Human friendly SPIR-V textual representation

SPIR-V is a binary IL that is not meant to be written by humans. But there are many cases where it is desirable to write/modify IL, so I have defined a textual representation that I believe is more convenient to work with than the raw disassembly format used in the SPIR-V specification.

I have chosen to use an LLVM-like representation, as I'm used to that format. A typical instruction is written as
%58 = OpIAdd s32 %57, %32
Constants may be written directly as operands to the instructions. For example, if %32 is a constant
%32 = OpConstant s32 1
then the instruction %58 above can be written as
%58 = OpIAdd s32 %57, 1
In the same way, decorations may be attached directly to the instructions instead of having separate decoration instructions at the top of the file. For example
OpDecorate %56, PrecisionMedium
%56 = OpFMul <4 x f32> %55, %54
can be written as
%56 = OpFMul PrecisionMedium <4 x f32> %55, %54
Names can be used instead of the <id> number
%tmp = OpLoad <4 x f32> %21
%56 = OpFMul <4 x f32> %tmp, %54
This makes the assembler allocate a numerical <id> and adds debug information with the name. In general, you do not need to specify things that the assembler can generate by itself, such as the constant and decoration instructions above, or the CFG — the assembler reorders the basic blocks when needed.

The SPIR-V format spreads some information over several instructions in different parts of the binary. This textual representation allows collecting those to one statement, so global variables may be written as
@gl_VertexID = Input s32 PrecisionHigh BuiltIn(5) NoStaticUse
which generates instructions
OpName %16, "gl_VertexID"
OpDecorate %16, PrecisionHigh
OpDecorate %16, BuiltIn, 5
OpDecorate %16, NoStaticUse
%15 = OpTypePointer Input, s32
%16 = OpVariable %15 Input
and function definitions can in a similar way be written as
define <4 x f32> @foo(<4 x f32> %a) {
instead of
OpName %12, "foo"
OpName %11, "a"
%10 = OpTypeFunction <4 x f32>, <4 x f32>
%12 = OpFunction %8 0, %10
%11 = OpFunctionParameter <4 x f32>

As an example of how this looks like, I have disassembled a shader using my format

The shader is the same as used in the raw disassembly example in the SPIR-V specification

An assembler/disassembler implementing most of the above is available in my spirv-tools github repository. The disassembler tries to take advantage of the syntactic sugar per default, which has the drawback that you do not have full control over <id> numbering etc., and you will in general get a different binary if you re-assemble the shader. But there is a command line option -r to disable this and output instructions exactly as in the binary, which is useful if you want to e.g. modify the code to trigger some special case in your compiler.

The implementation is rather rough right now, so it may not work on your favorite SPIR-V binary. But I'll spend some more time on this the coming weeks (I plan to to formalize and document the syntax, and fix the issues mentioned in the TODO file), so I expect to have a working assembler/disassembler well before the first Vulkan driver is available... :)

No comments:

Post a Comment