6.4 KiB
SPIR-V Dialect
This document defines the SPIR-V dialect in MLIR.
SPIR-V is the Khronos Group’s binary intermediate language for representing graphics shaders and compute kernels. It is adopted by multiple Khronos Group’s APIs, including Vulkan and OpenCL.
Design Principles
SPIR-V defines a stable binary format for hardware driver consumption. Regularity is one of the design goals of SPIR-V. All concepts are represented as SPIR-V instructions, including declaring extensions and capabilities, defining types and constants, defining functions, attaching additional properties to computation results, etc. This way favors driver consumption but not necessarily compiler transformations.
The purpose of the SPIR-V dialect is to serve as the "proxy" of the binary format and to facilitate transformations. Therefore, it should
- Be trivial to serialize into the SPIR-V binary format;
- Stay as the same semantic level and try to be a mechanical 1:1 mapping;
- But deviate representationally if possible with MLIR mechanisms.
Conventions
The SPIR-V dialect has the following conventions:
- The prefix for all SPIR-V types and operations are
spv.
. - Ops that directly correspond to instructions in the binary format have
CamelCase
names, for example,spv.FMul
; - Otherwise they have
snake_case
names. These ops are mostly for defining the SPIR-V structure, inclduing module, function, and module-level ops. For example,spv.module
,spv.constant
.
Module
A SPIR-V module is defined via the spv.module
op, which has one region that
contains one block. Model-level instructions, including function definitions,
are all placed inside the block. Functions are defined using the standard func
op.
Compared to the binary format, we adjust how certain module-level SPIR-V instructions are represented in the SPIR-V dialect. Notably,
- Requirements for capabilities, extensions, extended instruction sets,
addressing model, and memory model is conveyed using
spv.module
attributes. This is considered better because these information are for the exexcution environment. It's eaiser to probe them if on the module op itself. - Annotations/decoration instrutions are "folded" into the instructions they decorate and represented as attributes on those ops. This elimiates potential forward references of SSA values, improves IR readability, and makes querying the annotations more direct.
- Various constant instructions are represented by the same
spv.constant
op. Those instructions are just for constants of different types; using one op to represent them reduces IR verbosity and makes transformations less tedious.
Types
The SPIR-V dialect reuses standard integer, float, and vector types and defines the following dialect-specific types:
spirv-type ::= array-type
| pointer-type
| runtime-array-type
Array type
This corresponds to SPIR-V array type. Its syntax is
element-type ::= integer-type
| floating-point-type
| vector-type
| spirv-type
array-type ::= `!spv.array<` integer-literal `x` element-type `>`
For example,
!spv.array<4 x i32>
!spv.array<16 x vector<4 x f32>>
Image type
This corresponds to SPIR-V image type. Its syntax is
dim ::= `1D` | `2D` | `3D` | `Cube` | <and other SPIR-V Dim specifiers...>
depth-info ::= `NoDepth` | `IsDepth` | `DepthUnknown`
arrayed-info ::= `NonArrayed` | `Arrayed`
sampling-info ::= `SingleSampled` | `MultiSampled`
sampler-use-info ::= `SamplerUnknown` | `NeedSampler` | `NoSampler`
format ::= `Unknown` | `Rgba32f` | <and other SPIR-V Image Formats...>
image-type ::= `!spv.image<` element-type `,` dim `,` depth-info `,`
arrayed-info `,` sampling-info `,`
sampler-use-info `,` format `>`
For example,
!spv.image<f32, 1D, NoDepth, NonArrayed, SingleSampled, SamplerUnknown, Unknown>
!spv.image<f32, Cube, IsDepth, Arrayed, MultiSampled, NeedSampler, Rgba32f>
Pointer type
This corresponds to SPIR-V pointer type. Its syntax is
storage-class ::= `UniformConstant`
| `Uniform`
| `Workgroup`
| <and other storage classes...>
pointer-type ::= `!spv.ptr<` element-type `,` storage-class `>`
For example,
!spv.ptr<i32, Function>
!spv.ptr<vector<4 x f32>, Uniform>
Runtime array type
This corresponds to SPIR-V runtime array type. Its syntax is
runtime-array-type ::= `!spv.rtarray<` element-type `>`
For example,
!spv.rtarray<i32>
!spv.rtarray<vector<4 x f32>>
Struct type
This corresponds to SPIR-V struct type. Its syntax is
struct-type ::= `!spv.struct<` spirv-type (` [` integer-literal `]` )?
(`, ` spirv-type ( ` [` integer-literal `] ` )? )* `>`
For Example,
!spv.struct<f32>
!spv.struct<f32 [0]>
!spv.struct<f32, !spv.image<f32, 1D, NoDepth, NonArrayed, SingleSampled, SamplerUnknown, Unknown>>
!spv.struct<f32 [0], i32 [4]>
Serialization
The serialization library provides two entry points, mlir::spirv::serialize()
and mlir::spirv::deserialize()
, for converting a MLIR SPIR-V module to binary
format and back.
The purpose of this library is to enable importing SPIR-V binary modules to run transformations on them and exporting SPIR-V modules to be consumed by execution environments. The focus is transformations, which inevitably means changes to the binary module; so it is not designed to be a general tool for investigating the SPIR-V binary module and does not guarantee roundtrip equivalence (at least for now). For the latter, please use the assembler/disassembler in the SPIRV-Tools project.