xref: /llvm-project/mlir/docs/Dialects/SPIR-V.md (revision ec08c11878fd86a9919a19270d650a48ba52ba04)
1# SPIR-V Dialect
2
3This document describes the design of the SPIR-V dialect in MLIR. It lists
4various design choices we made for modeling different SPIR-V mechanisms, and
5their rationale.
6
7This document also explains in a high-level manner how different components are
8organized and implemented in the code and gives steps to follow for extending
9them.
10
11This document assumes familiarity with SPIR-V. [SPIR-V][Spirv] is the Khronos
12Group’s binary intermediate language for representing graphics shaders and
13compute kernels. It is adopted by multiple Khronos Group’s APIs, including
14Vulkan and OpenCL. It is fully defined in a
15[human-readable specification][SpirvSpec]; the syntax of various SPIR-V
16instructions are encoded in a [machine-readable grammar][SpirvGrammar].
17
18[TOC]
19
20## Design Guidelines
21
22SPIR-V is a binary intermediate language that serves dual purpose: on one side,
23it is an intermediate language to represent graphics shaders and compute kernels
24for high-level languages to target; on the other side, it defines a stable
25binary format for hardware driver consumption. As a result, SPIR-V has design
26principles pertain to not only intermediate language, but also binary format.
27For example, regularity is one of the design goals of SPIR-V. All concepts are
28represented as SPIR-V instructions, including declaring extensions and
29capabilities, defining types and constants, defining functions, attaching
30additional properties to computation results, etc. This way favors binary
31encoding and decoding for driver consumption but not necessarily compiler
32transformations.
33
34### Dialect design principles
35
36The main objective of the SPIR-V dialect is to be a proper intermediate
37representation (IR) to facilitate compiler transformations. While we still aim
38to support serializing to and deserializing from the binary format for various
39good reasons, the binary format and its concerns play less a role in the design
40of the SPIR-V dialect: when there is a trade-off to be made between favoring IR
41and supporting binary format, we lean towards the former.
42
43On the IR aspect, the SPIR-V dialect aims to model SPIR-V at the same semantic
44level. It is not intended to be a higher level or lower level abstraction than
45the SPIR-V specification. Those abstractions are easily outside the domain of
46SPIR-V and should be modeled with other proper dialects so they can be shared
47among various compilation paths. Because of the dual purpose of SPIR-V, SPIR-V
48dialect staying at the same semantic level as the SPIR-V specification also
49means we can still have straightforward serialization and deserialization for
50the majority of functionalities.
51
52To summarize, the SPIR-V dialect follows the following design principles:
53
54*   Stay as the same semantic level as the SPIR-V specification by having
55    one-to-one mapping for most concepts and entities.
56*   Adopt SPIR-V specification's syntax if possible, but deviate intentionally
57    to utilize MLIR mechanisms if it results in better representation and
58    benefits transformation.
59*   Be straightforward to serialize into and deserialize from the SPIR-V binary
60    format.
61
62SPIR-V is designed to be consumed by hardware drivers, so its representation is
63quite clear, yet verbose for some cases. Allowing representational deviation
64gives us the flexibility to reduce the verbosity by using MLIR mechanisms.
65
66### Dialect scopes
67
68SPIR-V supports multiple execution environments, specified by client APIs.
69Notable adopters include Vulkan and OpenCL. It follows that the SPIR-V dialect
70should support multiple execution environments if to be a proper proxy of SPIR-V
71in MLIR systems. The SPIR-V dialect is designed with these considerations: it
72has proper support for versions, extensions, and capabilities and is as
73extensible as SPIR-V specification.
74
75## Conventions
76
77The SPIR-V dialect adopts the following conventions for IR:
78
79*   The prefix for all SPIR-V types and operations are `spirv.`.
80*   All instructions in an extended instruction set are further qualified with
81    the extended instruction set's prefix. For example, all operations in the
82    GLSL extended instruction set have the prefix of `spirv.GL.`.
83*   Ops that directly mirror instructions in the specification have `CamelCase`
84    names that are the same as the instruction opnames (without the `Op`
85    prefix). For example, `spirv.FMul` is a direct mirror of `OpFMul` in the
86    specification. Such an op will be serialized into and deserialized from one
87    SPIR-V instruction.
88*   Ops with `snake_case` names are those that have different representation
89    from corresponding instructions (or concepts) in the specification. These
90    ops are mostly for defining the SPIR-V structure. For example, `spirv.module`
91    and `spirv.Constant`. They may correspond to one or more instructions during
92    (de)serialization.
93*   Ops with `mlir.snake_case` names are those that have no corresponding
94    instructions (or concepts) in the binary format. They are introduced to
95    satisfy MLIR structural requirements. For example, `spirv.mlir.merge`. They
96    map to no instructions during (de)serialization.
97
98(TODO: consider merging the last two cases and adopting `spirv.mlir.` prefix for
99them.)
100
101## Module
102
103A SPIR-V module is defined via the `spirv.module` op, which has one region that
104contains one block. Model-level instructions, including function definitions,
105are all placed inside the block. Functions are defined using the builtin `func`
106op.
107
108We choose to model a SPIR-V module with a dedicated `spirv.module` op based on the
109following considerations:
110
111*   It maps cleanly to a SPIR-V module in the specification.
112*   We can enforce SPIR-V specific verification that is suitable to be performed
113    at the module-level.
114*   We can attach additional model-level attributes.
115*   We can control custom assembly form.
116
117The `spirv.module` op's region cannot capture SSA values from outside, neither
118implicitly nor explicitly. The `spirv.module` op's region is closed as to what ops
119can appear inside: apart from the builtin `func` op, it can only contain ops
120from the SPIR-V dialect. The `spirv.module` op's verifier enforces this rule. This
121meaningfully guarantees that a `spirv.module` can be the entry point and boundary
122for serialization.
123
124### Module-level operations
125
126SPIR-V binary format defines the following [sections][SpirvLogicalLayout]:
127
1281.  Capabilities required by the module.
1291.  Extensions required by the module.
1301.  Extended instructions sets required by the module.
1311.  Addressing and memory model specification.
1321.  Entry point specifications.
1331.  Execution mode declarations.
1341.  Debug instructions.
1351.  Annotation/decoration instructions.
1361.  Type, constant, global variables.
1371.  Function declarations.
1381.  Function definitions.
139
140Basically, a SPIR-V binary module contains multiple module-level instructions
141followed by a list of functions. Those module-level instructions are essential
142and they can generate result ids referenced by functions, notably, declaring
143resource variables to interact with the execution environment.
144
145Compared to the binary format, we adjust how these module-level SPIR-V
146instructions are represented in the SPIR-V dialect:
147
148#### Use MLIR attributes for metadata
149
150*   Requirements for capabilities, extensions, extended instruction sets,
151    addressing model, and memory model are conveyed using `spirv.module`
152    attributes. This is considered better because these information are for the
153    execution environment. It's easier to probe them if on the module op itself.
154*   Annotations/decoration instructions are "folded" into the instructions they
155    decorate and represented as attributes on those ops. This eliminates
156    potential forward references of SSA values, improves IR readability, and
157    makes querying the annotations more direct. More discussions can be found in
158    the [`Decorations`](#decorations) section.
159
160#### Model types with MLIR custom types
161
162*   Types are represented using MLIR builtin types and SPIR-V dialect specific
163    types. There are no type declaration ops in the SPIR-V dialect. More
164    discussions can be found in the [Types](#types) section later.
165
166#### Unify and localize constants
167
168*   Various normal constant instructions are represented by the same
169    `spirv.Constant` op. Those instructions are just for constants of different
170    types; using one op to represent them reduces IR verbosity and makes
171    transformations less tedious.
172*   Normal constants are not placed in `spirv.module`'s region; they are localized
173    into functions. This is to make functions in the SPIR-V dialect to be
174    isolated and explicit capturing. Constants are cheap to duplicate given
175    attributes are made unique in `MLIRContext`.
176
177#### Adopt symbol-based global variables and specialization constant
178
179*   Global variables are defined with the `spirv.GlobalVariable` op. They do not
180    generate SSA values. Instead they have symbols and should be referenced via
181    symbols. To use global variables in a function block, `spirv.mlir.addressof` is
182    needed to turn the symbol into an SSA value.
183*   Specialization constants are defined with the `spirv.SpecConstant` op. Similar
184    to global variables, they do not generate SSA values and have symbols for
185    reference, too. `spirv.mlir.referenceof` is needed to turn the symbol into an SSA
186    value for use in a function block.
187
188The above choices enables functions in the SPIR-V dialect to be isolated and
189explicit capturing.
190
191#### Disallow implicit capturing in functions
192
193*   In SPIR-V specification, functions support implicit capturing: they can
194    reference SSA values defined in modules. In the SPIR-V dialect functions are
195    defined with `func` op, which disallows implicit capturing. This is more
196    friendly to compiler analyses and transformations. More discussions can be
197    found in the [Function](#function) section later.
198
199#### Model entry points and execution models as normal ops
200
201*   A SPIR-V module can have multiple entry points. And these entry points refer
202    to the function and interface variables. It’s not suitable to model them as
203    `spirv.module` op attributes. We can model them as normal ops of using symbol
204    references.
205*   Similarly for execution modes, which are coupled with entry points, we can
206    model them as normal ops in `spirv.module`'s region.
207
208## Decorations
209
210Annotations/decorations provide additional information on result ids. In SPIR-V,
211all instructions can generate result ids, including value-computing and
212type-defining ones.
213
214For decorations on value result ids, we can just have a corresponding attribute
215attached to the operation generating the SSA value. For example, for the
216following SPIR-V:
217
218```spirv
219OpDecorate %v1 RelaxedPrecision
220OpDecorate %v2 NoContraction
221...
222%v1 = OpFMul %float %0 %0
223%v2 = OpFMul %float %1 %1
224```
225
226We can represent them in the SPIR-V dialect as:
227
228```mlir
229%v1 = "spirv.FMul"(%0, %0) {RelaxedPrecision: unit} : (f32, f32) -> (f32)
230%v2 = "spirv.FMul"(%1, %1) {NoContraction: unit} : (f32, f32) -> (f32)
231```
232
233This approach benefits transformations. Essentially those decorations are just
234additional properties of the result ids (and thus their defining instructions).
235In SPIR-V binary format, they are just represented as instructions. Literally
236following SPIR-V binary format means we need to through def-use chains to find
237the decoration instructions and query information from them.
238
239For decorations on type result ids, notice that practically, only result ids
240generated from composite types (e.g., `OpTypeArray`, `OpTypeStruct`) need to be
241decorated for memory layouting purpose (e.g., `ArrayStride`, `Offset`, etc.);
242scalar/vector types are required to be uniqued in SPIR-V. Therefore, we can just
243encode them directly in the dialect-specific type.
244
245## Types
246
247Theoretically we can define all SPIR-V types using MLIR extensible type system,
248but other than representational purity, it does not buy us more. Instead, we
249need to maintain the code and invest in pretty printing them. So we prefer to
250use builtin types if possible.
251
252The SPIR-V dialect reuses builtin integer, float, and vector types:
253
254Specification                        | Dialect
255:----------------------------------: | :-------------------------------:
256`OpTypeBool`                         | `i1`
257`OpTypeFloat <bitwidth>`             | `f<bitwidth>`
258`OpTypeVector <scalar-type> <count>` | `vector<<count> x <scalar-type>>`
259
260For integer types, the SPIR-V dialect supports all signedness semantics
261(signless, signed, unsigned) in order to ease transformations from higher level
262dialects. However, SPIR-V spec only defines two signedness semantics state: 0
263indicates unsigned, or no signedness semantics, 1 indicates signed semantics. So
264both `iN` and `uiN` are serialized into the same `OpTypeInt N 0`. For
265deserialization, we always treat `OpTypeInt N 0` as `iN`.
266
267`mlir::NoneType` is used for SPIR-V `OpTypeVoid`; builtin function types are
268used for SPIR-V `OpTypeFunction` types.
269
270The SPIR-V dialect and defines the following dialect-specific types:
271
272```
273spirv-type ::= array-type
274             | image-type
275             | pointer-type
276             | runtime-array-type
277             | sampled-image-type
278             | struct-type
279```
280
281### Array type
282
283This corresponds to SPIR-V [array type][ArrayType]. Its syntax is
284
285```
286element-type ::= integer-type
287               | floating-point-type
288               | vector-type
289               | spirv-type
290
291array-type ::= `!spirv.array` `<` integer-literal `x` element-type
292               (`,` `stride` `=` integer-literal)? `>`
293```
294
295For example,
296
297```mlir
298!spirv.array<4 x i32>
299!spirv.array<4 x i32, stride = 4>
300!spirv.array<16 x vector<4 x f32>>
301```
302
303### Image type
304
305This corresponds to SPIR-V [image type][ImageType]. Its syntax is
306
307```
308dim ::= `1D` | `2D` | `3D` | `Cube` | <and other SPIR-V Dim specifiers...>
309
310depth-info ::= `NoDepth` | `IsDepth` | `DepthUnknown`
311
312arrayed-info ::= `NonArrayed` | `Arrayed`
313
314sampling-info ::= `SingleSampled` | `MultiSampled`
315
316sampler-use-info ::= `SamplerUnknown` | `NeedSampler` | `NoSampler`
317
318format ::= `Unknown` | `Rgba32f` | <and other SPIR-V Image Formats...>
319
320image-type ::= `!spirv.image<` element-type `,` dim `,` depth-info `,`
321                           arrayed-info `,` sampling-info `,`
322                           sampler-use-info `,` format `>`
323```
324
325For example,
326
327```mlir
328!spirv.image<f32, 1D, NoDepth, NonArrayed, SingleSampled, SamplerUnknown, Unknown>
329!spirv.image<f32, Cube, IsDepth, Arrayed, MultiSampled, NeedSampler, Rgba32f>
330```
331
332### Pointer type
333
334This corresponds to SPIR-V [pointer type][PointerType]. Its syntax is
335
336```
337storage-class ::= `UniformConstant`
338                | `Uniform`
339                | `Workgroup`
340                | <and other storage classes...>
341
342pointer-type ::= `!spirv.ptr<` element-type `,` storage-class `>`
343```
344
345For example,
346
347```mlir
348!spirv.ptr<i32, Function>
349!spirv.ptr<vector<4 x f32>, Uniform>
350```
351
352### Runtime array type
353
354This corresponds to SPIR-V [runtime array type][RuntimeArrayType]. Its syntax is
355
356```
357runtime-array-type ::= `!spirv.rtarray` `<` element-type (`,` `stride` `=` integer-literal)? `>`
358```
359
360For example,
361
362```mlir
363!spirv.rtarray<i32>
364!spirv.rtarray<i32, stride=4>
365!spirv.rtarray<vector<4 x f32>>
366```
367### Sampled image type
368
369This corresponds to SPIR-V [sampled image type][SampledImageType]. Its syntax is
370
371```
372sampled-image-type ::= `!spirv.sampled_image<!spirv.image<` element-type `,` dim `,` depth-info `,`
373                                                        arrayed-info `,` sampling-info `,`
374                                                        sampler-use-info `,` format `>>`
375```
376
377For example,
378
379```mlir
380!spirv.sampled_image<!spirv.image<f32, Dim1D, NoDepth, NonArrayed, SingleSampled, NoSampler, Unknown>>
381!spirv.sampled_image<!spirv.image<i32, Rect, DepthUnknown, Arrayed, MultiSampled, NeedSampler, R8ui>>
382```
383
384### Struct type
385
386This corresponds to SPIR-V [struct type][StructType]. Its syntax is
387
388```
389struct-member-decoration ::= integer-literal? spirv-decoration*
390struct-type ::= `!spirv.struct<` spirv-type (`[` struct-member-decoration `]`)?
391                     (`, ` spirv-type (`[` struct-member-decoration `]`)? `>`
392```
393
394For Example,
395
396```mlir
397!spirv.struct<f32>
398!spirv.struct<f32 [0]>
399!spirv.struct<f32, !spirv.image<f32, 1D, NoDepth, NonArrayed, SingleSampled, SamplerUnknown, Unknown>>
400!spirv.struct<f32 [0], i32 [4]>
401```
402
403## Function
404
405In SPIR-V, a function construct consists of multiple instructions involving
406`OpFunction`, `OpFunctionParameter`, `OpLabel`, `OpFunctionEnd`.
407
408```spirv
409// int f(int v) { return v; }
410%1 = OpTypeInt 32 0
411%2 = OpTypeFunction %1 %1
412%3 = OpFunction %1 %2
413%4 = OpFunctionParameter %1
414%5 = OpLabel
415%6 = OpReturnValue %4
416     OpFunctionEnd
417```
418
419This construct is very clear yet quite verbose. It is intended for driver
420consumption. There is little benefit to literally replicate this construct in
421the SPIR-V dialect. Instead, we reuse the builtin `func` op to express functions
422more concisely:
423
424```mlir
425func.func @f(%arg: i32) -> i32 {
426  "spirv.ReturnValue"(%arg) : (i32) -> (i32)
427}
428```
429
430A SPIR-V function can have at most one result. It cannot contain nested
431functions or non-SPIR-V operations. `spirv.module` verifies these requirements.
432
433A major difference between the SPIR-V dialect and the SPIR-V specification for
434functions is that the former are isolated and require explicit capturing, while
435the latter allows implicit capturing. In SPIR-V specification, functions can
436refer to SSA values (generated by constants, global variables, etc.) defined in
437modules. The SPIR-V dialect adjusted how constants and global variables are
438modeled to enable isolated functions. Isolated functions are more friendly to
439compiler analyses and transformations. This also enables the SPIR-V dialect to
440better utilize core infrastructure: many functionalities in the core
441infrastructure require ops to be isolated, e.g., the
442[greedy pattern rewriter][GreedyPatternRewriter] can only act on ops isolated
443from above.
444
445(TODO: create a dedicated `spirv.fn` op for SPIR-V functions.)
446
447## Operations
448
449In SPIR-V, instruction is a generalized concept; a SPIR-V module is just a
450sequence of instructions. Declaring types, expressing computations, annotating
451result ids, expressing control flows and others are all in the form of
452instructions.
453
454We only discuss instructions expressing computations here, which can be
455represented via SPIR-V dialect ops. Module-level instructions for declarations
456and definitions are represented differently in the SPIR-V dialect as explained
457earlier in the [Module-level operations](#module-level-operations) section.
458
459An instruction computes zero or one result from zero or more operands. The
460result is a new result id. An operand can be a result id generated by a previous
461instruction, an immediate value, or a case of an enum type. We can model result
462id operands and results with MLIR SSA values; for immediate value and enum
463cases, we can model them with MLIR attributes.
464
465For example,
466
467```spirv
468%i32 = OpTypeInt 32 0
469%c42 = OpConstant %i32 42
470...
471%3 = OpVariable %i32 Function 42
472%4 = OpIAdd %i32 %c42 %c42
473```
474
475can be represented in the dialect as
476
477```mlir
478%0 = "spirv.Constant"() { value = 42 : i32 } : () -> i32
479%1 = "spirv.Variable"(%0) { storage_class = "Function" } : (i32) -> !spirv.ptr<i32, Function>
480%2 = "spirv.IAdd"(%0, %0) : (i32, i32) -> i32
481```
482
483Operation documentation is written in each op's Op Definition Spec using
484TableGen. A markdown version of the doc can be generated using
485`mlir-tblgen -gen-doc` and is attached in the
486[Operation definitions](#operation-definitions) section.
487
488### Ops from extended instruction sets
489
490Analogically extended instruction set is a mechanism to import SPIR-V
491instructions within another namespace. [`GLSL.std.450`][GlslStd450] is an
492extended instruction set that provides common mathematical routines that should
493be supported. Instead of modeling `OpExtInstImport` as a separate op and use a
494single op to model `OpExtInst` for all extended instructions, we model each
495SPIR-V instruction in an extended instruction set as a separate op with the
496proper name prefix. For example, for
497
498```spirv
499%glsl = OpExtInstImport "GLSL.std.450"
500
501%f32 = OpTypeFloat 32
502%cst = OpConstant %f32 ...
503
504%1 = OpExtInst %f32 %glsl 28 %cst
505%2 = OpExtInst %f32 %glsl 31 %cst
506```
507
508we can have
509
510```mlir
511%1 = "spirv.GL.Log"(%cst) : (f32) -> (f32)
512%2 = "spirv.GL.Sqrt"(%cst) : (f32) -> (f32)
513```
514
515## Control Flow
516
517SPIR-V binary format uses merge instructions (`OpSelectionMerge` and
518`OpLoopMerge`) to declare structured control flow. They explicitly declare a
519header block before the control flow diverges and a merge block where control
520flow subsequently converges. These blocks delimit constructs that must nest, and
521can only be entered and exited in structured ways.
522
523In the SPIR-V dialect, we use regions to mark the boundary of a structured
524control flow construct. With this approach, it's easier to discover all blocks
525belonging to a structured control flow construct. It is also more idiomatic to
526MLIR system.
527
528We introduce a `spirv.mlir.selection` and `spirv.mlir.loop` op for structured selections and
529loops, respectively. The merge targets are the next ops following them. Inside
530their regions, a special terminator, `spirv.mlir.merge` is introduced for branching to
531the merge target.
532
533### Selection
534
535`spirv.mlir.selection` defines a selection construct. It contains one region. The
536region should contain at least two blocks: one selection header block and one
537merge block.
538
539*   The selection header block should be the first block. It should contain the
540    `spirv.BranchConditional` or `spirv.Switch` op.
541*   The merge block should be the last block. The merge block should only
542    contain a `spirv.mlir.merge` op. Any block can branch to the merge block for early
543    exit.
544
545```
546               +--------------+
547               | header block |                 (may have multiple outgoing branches)
548               +--------------+
549                    / | \
550                     ...
551
552
553   +---------+   +---------+   +---------+
554   | case #0 |   | case #1 |   | case #2 |  ... (may have branches between each other)
555   +---------+   +---------+   +---------+
556
557
558                     ...
559                    \ | /
560                      v
561               +-------------+
562               | merge block |                  (may have multiple incoming branches)
563               +-------------+
564```
565
566For example, for the given function
567
568```c++
569void loop(bool cond) {
570  int x = 0;
571  if (cond) {
572    x = 1;
573  } else {
574    x = 2;
575  }
576  // ...
577}
578```
579
580It will be represented as
581
582```mlir
583func.func @selection(%cond: i1) -> () {
584  %zero = spirv.Constant 0: i32
585  %one = spirv.Constant 1: i32
586  %two = spirv.Constant 2: i32
587  %x = spirv.Variable init(%zero) : !spirv.ptr<i32, Function>
588
589  spirv.mlir.selection {
590    spirv.BranchConditional %cond, ^then, ^else
591
592  ^then:
593    spirv.Store "Function" %x, %one : i32
594    spirv.Branch ^merge
595
596  ^else:
597    spirv.Store "Function" %x, %two : i32
598    spirv.Branch ^merge
599
600  ^merge:
601    spirv.mlir.merge
602  }
603
604  // ...
605}
606
607```
608
609### Loop
610
611`spirv.mlir.loop` defines a loop construct. It contains one region. The region should
612contain at least four blocks: one entry block, one loop header block, one loop
613continue block, one merge block.
614
615*   The entry block should be the first block and it should jump to the loop
616    header block, which is the second block.
617*   The merge block should be the last block. The merge block should only
618    contain a `spirv.mlir.merge` op. Any block except the entry block can branch to
619    the merge block for early exit.
620*   The continue block should be the second to last block and it should have a
621    branch to the loop header block.
622*   The loop continue block should be the only block, except the entry block,
623    branching to the loop header block.
624
625```
626    +-------------+
627    | entry block |           (one outgoing branch)
628    +-------------+
629           |
630           v
631    +-------------+           (two incoming branches)
632    | loop header | <-----+   (may have one or two outgoing branches)
633    +-------------+       |
634                          |
635          ...             |
636         \ | /            |
637           v              |
638   +---------------+      |   (may have multiple incoming branches)
639   | loop continue | -----+   (may have one or two outgoing branches)
640   +---------------+
641
642          ...
643         \ | /
644           v
645    +-------------+           (may have multiple incoming branches)
646    | merge block |
647    +-------------+
648```
649
650The reason to have another entry block instead of directly using the loop header
651block as the entry block is to satisfy region's requirement: entry block of
652region may not have predecessors. We have a merge block so that branch ops can
653reference it as successors. The loop continue block here corresponds to
654"continue construct" using SPIR-V spec's term; it does not mean the "continue
655block" as defined in the SPIR-V spec, which is "a block containing a branch to
656an OpLoopMerge instruction’s Continue Target."
657
658For example, for the given function
659
660```c++
661void loop(int count) {
662  for (int i = 0; i < count; ++i) {
663    // ...
664  }
665}
666```
667
668It will be represented as
669
670```mlir
671func.func @loop(%count : i32) -> () {
672  %zero = spirv.Constant 0: i32
673  %one = spirv.Constant 1: i32
674  %var = spirv.Variable init(%zero) : !spirv.ptr<i32, Function>
675
676  spirv.mlir.loop {
677    spirv.Branch ^header
678
679  ^header:
680    %val0 = spirv.Load "Function" %var : i32
681    %cmp = spirv.SLessThan %val0, %count : i32
682    spirv.BranchConditional %cmp, ^body, ^merge
683
684  ^body:
685    // ...
686    spirv.Branch ^continue
687
688  ^continue:
689    %val1 = spirv.Load "Function" %var : i32
690    %add = spirv.IAdd %val1, %one : i32
691    spirv.Store "Function" %var, %add : i32
692    spirv.Branch ^header
693
694  ^merge:
695    spirv.mlir.merge
696  }
697  return
698}
699```
700
701### Block argument for Phi
702
703There are no direct Phi operations in the SPIR-V dialect; SPIR-V `OpPhi`
704instructions are modelled as block arguments in the SPIR-V dialect. (See the
705[Rationale][Rationale] doc for "Block Arguments vs Phi nodes".) Each block
706argument corresponds to one `OpPhi` instruction in the SPIR-V binary format. For
707example, for the following SPIR-V function `foo`:
708
709```spirv
710  %foo = OpFunction %void None ...
711%entry = OpLabel
712  %var = OpVariable %_ptr_Function_int Function
713         OpSelectionMerge %merge None
714         OpBranchConditional %true %true %false
715 %true = OpLabel
716         OpBranch %phi
717%false = OpLabel
718         OpBranch %phi
719  %phi = OpLabel
720  %val = OpPhi %int %int_1 %false %int_0 %true
721         OpStore %var %val
722         OpReturn
723%merge = OpLabel
724         OpReturn
725         OpFunctionEnd
726```
727
728It will be represented as:
729
730```mlir
731func.func @foo() -> () {
732  %var = spirv.Variable : !spirv.ptr<i32, Function>
733
734  spirv.mlir.selection {
735    %true = spirv.Constant true
736    spirv.BranchConditional %true, ^true, ^false
737
738  ^true:
739    %zero = spirv.Constant 0 : i32
740    spirv.Branch ^phi(%zero: i32)
741
742  ^false:
743    %one = spirv.Constant 1 : i32
744    spirv.Branch ^phi(%one: i32)
745
746  ^phi(%arg: i32):
747    spirv.Store "Function" %var, %arg : i32
748    spirv.Return
749
750  ^merge:
751    spirv.mlir.merge
752  }
753  spirv.Return
754}
755```
756
757## Version, extensions, capabilities
758
759SPIR-V supports versions, extensions, and capabilities as ways to indicate the
760availability of various features (types, ops, enum cases) on target hardware.
761For example, non-uniform group operations were missing before v1.3, and they
762require special capabilities like `GroupNonUniformArithmetic` to be used. These
763availability information relates to [target environment](#target-environment)
764and affects the legality of patterns during dialect conversion.
765
766SPIR-V ops' availability requirements are modeled with
767[op interfaces][MlirOpInterface]:
768
769*   `QueryMinVersionInterface` and `QueryMaxVersionInterface` for version
770    requirements
771*   `QueryExtensionInterface` for extension requirements
772*   `QueryCapabilityInterface` for capability requirements
773
774These interface declarations are auto-generated from TableGen definitions
775included in [`SPIRVBase.td`][MlirSpirvBase]. At the moment all SPIR-V ops
776implement the above interfaces.
777
778SPIR-V ops' availability implementation methods are automatically synthesized
779from the availability specification on each op and enum attribute in TableGen.
780An op needs to look into not only the opcode but also operands to derive its
781availability requirements. For example, `spirv.ControlBarrier` requires no
782special capability if the execution scope is `Subgroup`, but it will require
783the `VulkanMemoryModel` capability if the scope is `QueueFamily`.
784
785SPIR-V types' availability implementation methods are manually written as
786overrides in the SPIR-V [type hierarchy][MlirSpirvTypes].
787
788These availability requirements serve as the "ingredients" for the
789[`SPIRVConversionTarget`](#spirvconversiontarget) and
790[`SPIRVTypeConverter`](#spirvtypeconverter) to perform op and type conversions,
791by following the requirements in [target environment](#target-environment).
792
793## Target environment
794
795SPIR-V aims to support multiple execution environments as specified by client
796APIs. These execution environments affect the availability of certain SPIR-V
797features. For example, a [Vulkan 1.1][VulkanSpirv] implementation must support
798the 1.0, 1.1, 1.2, and 1.3 versions of SPIR-V and the 1.0 version of the SPIR-V
799extended instructions for GLSL. Further Vulkan extensions may enable more SPIR-V
800instructions.
801
802SPIR-V compilation should also take into consideration of the execution
803environment, so we generate SPIR-V modules valid for the target environment.
804This is conveyed by the `spirv.target_env` (`spirv::TargetEnvAttr`) attribute. It
805should be of `#spirv.target_env` attribute kind, which is defined as:
806
807```
808spirv-version    ::= `v1.0` | `v1.1` | ...
809spirv-extension  ::= `SPV_KHR_16bit_storage` | `SPV_EXT_physical_storage_buffer` | ...
810spirv-capability ::= `Shader` | `Kernel` | `GroupNonUniform` | ...
811
812spirv-extension-list     ::= `[` (spirv-extension-elements)? `]`
813spirv-extension-elements ::= spirv-extension (`,` spirv-extension)*
814
815spirv-capability-list     ::= `[` (spirv-capability-elements)? `]`
816spirv-capability-elements ::= spirv-capability (`,` spirv-capability)*
817
818spirv-resource-limits ::= dictionary-attribute
819
820spirv-vce-attribute ::= `#` `spirv.vce` `<`
821                            spirv-version `,`
822                            spirv-capability-list `,`
823                            spirv-extensions-list `>`
824
825spirv-vendor-id ::= `AMD` | `NVIDIA` | ...
826spirv-device-type ::= `DiscreteGPU` | `IntegratedGPU` | `CPU` | ...
827spirv-device-id ::= integer-literal
828spirv-device-info ::= spirv-vendor-id (`:` spirv-device-type (`:` spirv-device-id)?)?
829
830spirv-target-env-attribute ::= `#` `spirv.target_env` `<`
831                                  spirv-vce-attribute,
832                                  (spirv-device-info `,`)?
833                                  spirv-resource-limits `>`
834```
835
836The attribute has a few fields:
837
838*   A `#spirv.vce` (`spirv::VerCapExtAttr`) attribute:
839    *   The target SPIR-V version.
840    *   A list of SPIR-V extensions for the target.
841    *   A list of SPIR-V capabilities for the target.
842*   A dictionary of target resource limits (see the
843    [Vulkan spec][VulkanResourceLimits] for explanation):
844    *   `max_compute_workgroup_invocations`
845    *   `max_compute_workgroup_size`
846
847For example,
848
849```
850module attributes {
851spirv.target_env = #spirv.target_env<
852    #spirv.vce<v1.3, [Shader, GroupNonUniform], [SPV_KHR_8bit_storage]>,
853    ARM:IntegratedGPU,
854    {
855      max_compute_workgroup_invocations = 128 : i32,
856      max_compute_workgroup_size = dense<[128, 128, 64]> : vector<3xi32>
857    }>
858} { ... }
859```
860
861Dialect conversion framework will utilize the information in `spirv.target_env` to
862properly filter out patterns and ops not available in the target execution
863environment. When targeting SPIR-V, one needs to create a
864[`SPIRVConversionTarget`](#spirvconversiontarget) by providing such an
865attribute.
866
867## Shader interface (ABI)
868
869SPIR-V itself is just expressing computation happening on GPU device. SPIR-V
870programs themselves are not enough for running workloads on GPU; a companion
871host application is needed to manage the resources referenced by SPIR-V programs
872and dispatch the workload. For the Vulkan execution environment, the host
873application will be written using Vulkan API. Unlike CUDA, the SPIR-V program
874and the Vulkan application are typically authored with different front-end
875languages, which isolates these two worlds. Yet they still need to match
876_interfaces_: the variables declared in a SPIR-V program for referencing
877resources need to match with the actual resources managed by the application
878regarding their parameters.
879
880Still using Vulkan as an example execution environment, there are two primary
881resource types in Vulkan: buffers and images. They are used to back various uses
882that may differ regarding the classes of operations (load, store, atomic) to be
883performed. These uses are differentiated via descriptor types. (For example,
884uniform storage buffer descriptors can only support load operations while
885storage buffer descriptors can support load, store, and atomic operations.)
886Vulkan uses a binding model for resources. Resources are associated with
887descriptors and descriptors are further grouped into sets. Each descriptor thus
888has a set number and a binding number. Descriptors in the application
889corresponds to variables in the SPIR-V program. Their parameters must match,
890including but not limited to set and binding numbers.
891
892Apart from buffers and images, there is other data that is set up by Vulkan and
893referenced inside the SPIR-V program, for example, push constants. They also
894have parameters that require matching between the two worlds.
895
896The interface requirements are external information to the SPIR-V compilation
897path in MLIR. Besides, each Vulkan application may want to handle resources
898differently. To avoid duplication and to share common utilities, a SPIR-V shader
899interface specification needs to be defined to provide the external requirements
900to and guide the SPIR-V compilation path.
901
902### Shader interface attributes
903
904The SPIR-V dialect defines [a few attributes][MlirSpirvAbi] for specifying these
905interfaces:
906
907*   `spirv.entry_point_abi` is a struct attribute that should be attached to the
908    entry function. It contains:
909    *   `local_size` for specifying the local work group size for the dispatch.
910*   `spirv.interface_var_abi` is attribute that should be attached to each operand
911    and result of the entry function. It should be of `#spirv.interface_var_abi`
912    attribute kind, which is defined as:
913
914```
915spv-storage-class     ::= `StorageBuffer` | ...
916spv-descriptor-set    ::= integer-literal
917spv-binding           ::= integer-literal
918spv-interface-var-abi ::= `#` `spirv.interface_var_abi` `<(` spv-descriptor-set
919                          `,` spv-binding `)` (`,` spv-storage-class)? `>`
920```
921
922For example,
923
924```
925#spirv.interface_var_abi<(0, 0), StorageBuffer>
926#spirv.interface_var_abi<(0, 1)>
927```
928
929The attribute has a few fields:
930
931*   Descriptor set number for the corresponding resource variable.
932*   Binding number for the corresponding resource variable.
933*   Storage class for the corresponding resource variable.
934
935The SPIR-V dialect provides a [`LowerABIAttributesPass`][MlirSpirvPasses] that
936uses this information to lower the entry point function and its ABI consistent
937with the Vulkan validation rules. Specifically,
938
939*   Creates `spirv.GlobalVariable`s for the arguments, and replaces all uses of
940    the argument with this variable. The SSA value used for replacement is
941    obtained using the `spirv.mlir.addressof` operation.
942*   Adds the `spirv.EntryPoint` and `spirv.ExecutionMode` operations into the
943    `spirv.module` for the entry function.
944
945## Serialization and deserialization
946
947Although the main objective of the SPIR-V dialect is to act as a proper IR for
948compiler transformations, being able to serialize to and deserialize from the
949binary format is still very valuable for many good reasons. Serialization
950enables the artifacts of SPIR-V compilation to be consumed by an execution
951environment; deserialization allows us to import SPIR-V binary modules and run
952transformations on them. So serialization and deserialization are supported from
953the very beginning of the development of the SPIR-V dialect.
954
955The serialization library provides two entry points, `mlir::spirv::serialize()`
956and `mlir::spirv::deserialize()`, for converting a MLIR SPIR-V module to binary
957format and back. The [Code organization](#code-organization) explains more about
958this.
959
960Given that the focus is transformations, which inevitably means changes to the
961binary module; so serialization is not designed to be a general tool for
962investigating the SPIR-V binary module and does not guarantee roundtrip
963equivalence (at least for now). For the latter, please use the
964assembler/disassembler in the [SPIRV-Tools][SpirvTools] project.
965
966A few transformations are performed in the process of serialization because of
967the representational differences between SPIR-V dialect and binary format:
968
969*   Attributes on `spirv.module` are emitted as their corresponding SPIR-V
970    instructions.
971*   Types are serialized into `OpType*` instructions in the SPIR-V binary module
972    section for types, constants, and global variables.
973*   `spirv.Constant`s are unified and placed in the SPIR-V binary module section
974    for types, constants, and global variables.
975*   Attributes on ops, if not part of the op's binary encoding, are emitted as
976    `OpDecorate*` instructions in the SPIR-V binary module section for
977    decorations.
978*   `spirv.mlir.selection`s and `spirv.mlir.loop`s are emitted as basic blocks with `Op*Merge`
979    instructions in the header block as required by the binary format.
980*   Block arguments are materialized as `OpPhi` instructions at the beginning of
981    the corresponding blocks.
982
983Similarly, a few transformations are performed during deserialization:
984
985*   Instructions for execution environment requirements (extensions,
986    capabilities, extended instruction sets, etc.) will be placed as attributes
987    on `spirv.module`.
988*   `OpType*` instructions will be converted into proper `mlir::Type`s.
989*   `OpConstant*` instructions are materialized as `spirv.Constant` at each use
990    site.
991*   `OpVariable` instructions will be converted to `spirv.GlobalVariable` ops if
992    in module-level; otherwise they will be converted into `spirv.Variable` ops.
993*   Every use of a module-level `OpVariable` instruction will materialize a
994    `spirv.mlir.addressof` op to turn the symbol of the corresponding
995    `spirv.GlobalVariable` into an SSA value.
996*   Every use of a `OpSpecConstant` instruction will materialize a
997    `spirv.mlir.referenceof` op to turn the symbol of the corresponding
998    `spirv.SpecConstant` into an SSA value.
999*   `OpPhi` instructions are converted to block arguments.
1000*   Structured control flow are placed inside `spirv.mlir.selection` and `spirv.mlir.loop`.
1001
1002## Conversions
1003
1004One of the main features of MLIR is the ability to progressively lower from
1005dialects that capture programmer abstraction into dialects that are closer to a
1006machine representation, like SPIR-V dialect. This progressive lowering through
1007multiple dialects is enabled through the use of the
1008[DialectConversion][MlirDialectConversion] framework in MLIR. To simplify
1009targeting SPIR-V dialect using the Dialect Conversion framework, two utility
1010classes are provided.
1011
1012(**Note** : While SPIR-V has some [validation rules][SpirvShaderValidation],
1013additional rules are imposed by [Vulkan execution environment][VulkanSpirv]. The
1014lowering described below implements both these requirements.)
1015
1016### `SPIRVConversionTarget`
1017
1018The `mlir::spirv::SPIRVConversionTarget` class derives from the
1019`mlir::ConversionTarget` class and serves as a utility to define a conversion
1020target satisfying a given [`spirv.target_env`](#target-environment). It registers
1021proper hooks to check the dynamic legality of SPIR-V ops. Users can further
1022register other legality constraints into the returned `SPIRVConversionTarget`.
1023
1024`spirv::lookupTargetEnvOrDefault()` is a handy utility function to query an
1025`spirv.target_env` attached in the input IR or use the default to construct a
1026`SPIRVConversionTarget`.
1027
1028### `SPIRVTypeConverter`
1029
1030The `mlir::SPIRVTypeConverter` derives from `mlir::TypeConverter` and provides
1031type conversion for builtin types to SPIR-V types conforming to the
1032[target environment](#target-environment) it is constructed with. If the
1033required extension/capability for the resultant type is not available in the
1034given target environment, `convertType()` will return a null type.
1035
1036Builtin scalar types are converted to their corresponding SPIR-V scalar types.
1037
1038(TODO: Note that if the bitwidth is not available in the target environment,
1039it will be unconditionally converted to 32-bit. This should be switched to
1040properly emulating non-32-bit scalar types.)
1041
1042[Builtin index type][MlirIndexType] need special handling since they are not
1043directly supported in SPIR-V. Currently the `index` type is converted to `i32`.
1044
1045(TODO: Allow for configuring the integer width to use for `index` types in the
1046SPIR-V dialect)
1047
1048SPIR-V only supports vectors of 2/3/4 elements; so
1049[builtin vector types][MlirVectorType] of these lengths can be converted
1050directly.
1051
1052(TODO: Convert other vectors of lengths to scalars or arrays)
1053
1054[Builtin memref types][MlirMemrefType] with static shape and stride are
1055converted to `spirv.ptr<spirv.struct<spirv.array<...>>>`s. The resultant SPIR-V array
1056types have the same element type as the source memref and its number of elements
1057is obtained from the layout specification of the memref. The storage class of
1058the pointer type are derived from the memref's memory space with
1059`SPIRVTypeConverter::getStorageClassForMemorySpace()`.
1060
1061### Utility functions for lowering
1062
1063#### Setting layout for shader interface variables
1064
1065SPIR-V validation rules for shaders require composite objects to be explicitly
1066laid out. If a `spirv.GlobalVariable` is not explicitly laid out, the utility
1067method `mlir::spirv::decorateType` implements a layout consistent with
1068the [Vulkan shader requirements][VulkanShaderInterface].
1069
1070#### Creating builtin variables
1071
1072In SPIR-V dialect, builtins are represented using `spirv.GlobalVariable`s, with
1073`spirv.mlir.addressof` used to get a handle to the builtin as an SSA value.  The
1074method `mlir::spirv::getBuiltinVariableValue` creates a `spirv.GlobalVariable` for
1075the builtin in the current `spirv.module` if it does not exist already, and
1076returns an SSA value generated from an `spirv.mlir.addressof` operation.
1077
1078### Current conversions to SPIR-V
1079
1080Using the above infrastructure, conversions are implemented from
1081
1082*   [Arith Dialect][MlirArithDialect]
1083*   [GPU Dialect][MlirGpuDialect] : A gpu.module is converted to a `spirv.module`.
1084    A gpu.function within this module is lowered as an entry function.
1085
1086## Code organization
1087
1088We aim to provide multiple libraries with clear dependencies for SPIR-V related
1089functionalities in MLIR so developers can just choose the needed components
1090without pulling in the whole world.
1091
1092### The dialect
1093
1094The code for the SPIR-V dialect resides in a few places:
1095
1096*   Public headers are placed in [include/mlir/Dialect/SPIRV][MlirSpirvHeaders].
1097*   Libraries are placed in [lib/Dialect/SPIRV][MlirSpirvLibs].
1098*   IR tests are placed in [test/Dialect/SPIRV][MlirSpirvTests].
1099*   Unit tests are placed in [unittests/Dialect/SPIRV][MlirSpirvUnittests].
1100
1101The whole SPIR-V dialect is exposed via multiple headers for better
1102organization:
1103
1104*   [SPIRVDialect.h][MlirSpirvDialect] defines the SPIR-V dialect.
1105*   [SPIRVTypes.h][MlirSpirvTypes] defines all SPIR-V specific types.
1106*   [SPIRVOps.h][MlirSPirvOpsH] defines all SPIR-V operations.
1107*   [Serialization.h][MlirSpirvSerialization] defines the entry points for
1108    serialization and deserialization.
1109
1110The dialect itself, including all types and ops, is in the `MLIRSPIRV` library.
1111Serialization functionalities are in the `MLIRSPIRVSerialization` library.
1112
1113### Op definitions
1114
1115We use [Op Definition Spec][ODS] to define all SPIR-V ops. They are written in
1116TableGen syntax and placed in various `*Ops.td` files in the header directory.
1117Those `*Ops.td` files are organized according to the instruction categories used
1118in the SPIR-V specification, for example, an op belonging to the "Atomics
1119Instructions" section is put in the `SPIRVAtomicOps.td` file.
1120
1121`SPIRVOps.td` serves as the main op definition file that includes all files
1122for specific categories.
1123
1124`SPIRVBase.td` defines common classes and utilities used by various op
1125definitions. It contains the TableGen SPIR-V dialect definition, SPIR-V
1126versions, known extensions, various SPIR-V enums, TableGen SPIR-V types, and
1127base op classes, etc.
1128
1129Many of the contents in `SPIRVBase.td`, e.g., the opcodes and various enums, and
1130all `*Ops.td` files can be automatically updated via a Python script, which
1131queries the SPIR-V specification and grammar. This greatly reduces the burden of
1132supporting new ops and keeping updated with the SPIR-V spec. More details on
1133this automated development can be found in the
1134[Automated development flow](#automated-development-flow) section.
1135
1136### Dialect conversions
1137
1138The code for conversions from other dialects to the SPIR-V dialect also resides
1139in a few places:
1140
1141*   From GPU dialect: headers are at
1142    [include/mlir/Conversion/GPUTOSPIRV][MlirGpuToSpirvHeaders]; libraries are
1143    at [lib/Conversion/GPUToSPIRV][MlirGpuToSpirvLibs].
1144*   From Func dialect: headers are at
1145    [include/mlir/Conversion/FuncToSPIRV][MlirFuncToSpirvHeaders]; libraries
1146    are at [lib/Conversion/FuncToSPIRV][MlirFuncToSpirvLibs].
1147
1148These dialect to dialect conversions have their dedicated libraries,
1149`MLIRGPUToSPIRV` and `MLIRFuncToSPIRV`, respectively.
1150
1151There are also common utilities when targeting SPIR-V from any dialect:
1152
1153*   [include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h][MlirSpirvConversion]
1154    contains type converters and other utility functions.
1155*   [include/mlir/Dialect/SPIRV/Transforms/Passes.h][MlirSpirvPasses] contains
1156    SPIR-V specific analyses and transformations.
1157
1158These common utilities are implemented in the `MLIRSPIRVConversion` and
1159`MLIRSPIRVTransforms` library, respectively.
1160
1161## Rationale
1162
1163### Lowering `memref`s to `!spirv.array<..>` and `!spirv.rtarray<..>`.
1164
1165The LLVM dialect lowers `memref` types to a `MemrefDescriptor`:
1166
1167```
1168struct MemrefDescriptor {
1169  void *allocated_ptr; // Pointer to the base allocation.
1170  void *aligned_ptr;   // Pointer within base allocation which is aligned to
1171                       // the value set in the memref.
1172  size_t offset;       // Offset from aligned_ptr from where to get values
1173                       // corresponding to the memref.
1174  size_t shape[rank];  // Shape of the memref.
1175  size_t stride[rank]; // Strides used while accessing elements of the memref.
1176};
1177```
1178
1179In SPIR-V dialect, we chose not to use a `MemrefDescriptor`. Instead a `memref`
1180is lowered directly to a `!spirv.ptr<!spirv.array<nelts x elem_type>>` when the
1181`memref` is statically shaped, and `!spirv.ptr<!spirv.rtarray<elem_type>>` when the
1182`memref` is dynamically shaped. The rationale behind this choice is described
1183below.
1184
11851.  Inputs/output buffers to a SPIR-V kernel are specified using
1186    [`OpVariable`][SpirvOpVariable] inside [interface storage
1187    classes][VulkanShaderInterfaceStorageClass] (e.g., Uniform, StorageBuffer,
1188    etc.), while kernel private variables reside in non-interface storage
1189    classes (e.g., Function, Workgroup, etc.). By default, Vulkan-flavored
1190    SPIR-V requires logical addressing mode: one cannot load/store pointers
1191    from/to variables and cannot perform pointer arithmetic.  Expressing a
1192    struct like `MemrefDescriptor` in interface storage class requires special
1193    addressing mode
1194    ([PhysicalStorageBuffer][VulkanExtensionPhysicalStorageBuffer]) and
1195    manipulating such a struct in non-interface storage classes requires special
1196    capabilities ([VariablePointers][VulkanExtensionVariablePointers]).
1197    Requiring these two extensions together will significantly limit the
1198    Vulkan-capable device we can target; basically ruling out mobile support..
1199
12001.  An alternative to having one level of indirection (as is the case with
1201    `MemrefDescriptor`s), is to embed the `!spirv.array` or `!spirv.rtarray`
1202    directly in the `MemrefDescriptor`, Having such a descriptor at the ABI
1203    boundary implies that the first few bytes of the input/output buffers would
1204    need to be reserved for shape/stride information. This adds an unnecessary
1205    burden on the host side.
1206
12071.  A more performant approach would be to have the data be an `OpVariable`,
1208    with the shape and strides passed using a separate `OpVariable`. This has
1209    further advantages:
1210
1211    *   All the dynamic shape/stride information of the `memref` can be combined
1212        into a single descriptor. Descriptors are [limited resources on many
1213        Vulkan hardware][VulkanGPUInfoMaxPerStageDescriptorStorageBuffers].  So
1214        combining them would help make the generated code more portable across
1215        devices.
1216    *   If the shape/stride information is small enough, they could be accessed
1217        using [PushConstants][VulkanPushConstants] that are faster to access and
1218        avoid buffer allocation overheads. These would be unnecessary if all
1219        shapes are static. In the dynamic shape cases, a few parameters are
1220        typically enough to compute the shape of all `memref`s used/referenced
1221        within the kernel making the use of PushConstants possible.
1222    *   The shape/stride information (typically) needs to be update less
1223        frequently than the data stored in the buffers. They could be part of
1224        different descriptor sets.
1225
1226## Contribution
1227
1228All kinds of contributions are highly appreciated! :) We have GitHub issues for
1229tracking the [dialect][GitHubDialectTracking] and
1230[lowering][GitHubLoweringTracking] development. You can find todo tasks there.
1231The [Code organization](#code-organization) section gives an overview of how
1232SPIR-V related functionalities are implemented in MLIR. This section gives more
1233concrete steps on how to contribute.
1234
1235### Automated development flow
1236
1237One of the goals of SPIR-V dialect development is to leverage both the SPIR-V
1238[human-readable specification][SpirvSpec] and
1239[machine-readable grammar][SpirvGrammar] to auto-generate as much contents as
1240possible. Specifically, the following tasks can be automated (partially or
1241fully):
1242
1243*   Adding support for a new operation.
1244*   Adding support for a new SPIR-V enum.
1245*   Serialization and deserialization of a new operation.
1246
1247We achieve this using the Python script
1248[`gen_spirv_dialect.py`][GenSpirvUtilsPy]. It fetches the human-readable
1249specification and machine-readable grammar directly from the Internet and
1250updates various SPIR-V `*.td` files in place. The script gives us an automated
1251flow for adding support for new ops or enums.
1252
1253Afterwards, we have SPIR-V specific `mlir-tblgen` backends for reading the Op
1254Definition Spec and generate various components, including (de)serialization
1255logic for ops. Together with standard `mlir-tblgen` backends, we auto-generate
1256all op classes, enum classes, etc.
1257
1258In the following subsections, we list the detailed steps to follow for common
1259tasks.
1260
1261### Add a new op
1262
1263To add a new op, invoke the `define_inst.sh` script wrapper in utils/spirv.
1264`define_inst.sh` requires a few parameters:
1265
1266```sh
1267./define_inst.sh <filename> <base-class-name> <opname>
1268```
1269
1270For example, to define the op for `OpIAdd`, invoke
1271
1272```sh
1273./define_inst.sh SPIRVArithmeticOps.td ArithmeticBinaryOp OpIAdd
1274```
1275
1276where `SPIRVArithmeticOps.td` is the filename for hosting the new op and
1277`ArithmeticBinaryOp` is the direct base class the newly defined op will derive
1278from.
1279
1280Similarly, to define the op for `OpAtomicAnd`,
1281
1282```sh
1283./define_inst.sh SPIRVAtomicOps.td AtomicUpdateWithValueOp OpAtomicAnd
1284```
1285
1286Note that the generated SPIR-V op definition is just a best-effort template; it
1287is still expected to be updated to have more accurate traits, arguments, and
1288results.
1289
1290It is also expected that a custom assembly form is defined for the new op,
1291which will require providing the parser and printer. The EBNF form of the
1292custom assembly should be described in the op's description and the parser
1293and printer should be placed in [`SPIRVOps.cpp`][MlirSpirvOpsCpp] with the
1294following signatures:
1295
1296```c++
1297static ParseResult parse<spirv-op-symbol>Op(OpAsmParser &parser,
1298                                            OperationState &state);
1299static void print(spirv::<spirv-op-symbol>Op op, OpAsmPrinter &printer);
1300```
1301
1302See any existing op as an example.
1303
1304Verification should be provided for the new op to cover all the rules described
1305in the SPIR-V specification. Choosing the proper ODS types and attribute kinds,
1306which can be found in [`SPIRVBase.td`][MlirSpirvBase], can help here. Still
1307sometimes we need to manually write additional verification logic in
1308[`SPIRVOps.cpp`][MlirSpirvOpsCpp] in a function with the following signature:
1309
1310```c++
1311LogicalResult spirv::<spirv-op-symbol>Op::verify();
1312```
1313
1314See any such function in [`SPIRVOps.cpp`][MlirSpirvOpsCpp] as an example.
1315
1316If no additional verification is needed, one needs to add the following to
1317the op's Op Definition Spec:
1318
1319```
1320let hasVerifier = 0;
1321```
1322
1323To suppress the requirement of the above C++ verification function.
1324
1325Tests for the op's custom assembly form and verification should be added to
1326the proper file in test/Dialect/SPIRV/.
1327
1328The generated op will automatically gain the logic for (de)serialization.
1329However, tests still need to be coupled with the change to make sure no
1330surprises. Serialization tests live in test/Dialect/SPIRV/Serialization.
1331
1332### Add a new enum
1333
1334To add a new enum, invoke the `define_enum.sh` script wrapper in utils/spirv.
1335`define_enum.sh` expects the following parameters:
1336
1337```sh
1338./define_enum.sh <enum-class-name>
1339```
1340
1341For example, to add the definition for SPIR-V storage class in to
1342`SPIRVBase.td`:
1343
1344```sh
1345./define_enum.sh StorageClass
1346```
1347
1348### Add a new custom type
1349
1350SPIR-V specific types are defined in [`SPIRVTypes.h`][MlirSpirvTypes]. See
1351examples there and the [tutorial][CustomTypeAttrTutorial] for defining new
1352custom types.
1353
1354### Add a new conversion
1355
1356To add conversion for a type update the `mlir::spirv::SPIRVTypeConverter` to
1357return the converted type (must be a valid SPIR-V type). See [Type
1358Conversion][MlirDialectConversionTypeConversion] for more details.
1359
1360To lower an operation into SPIR-V dialect, implement a [conversion
1361pattern][MlirDialectConversionRewritePattern]. If the conversion requires type
1362conversion as well, the pattern must inherit from the
1363`mlir::spirv::SPIRVOpLowering` class to get access to
1364`mlir::spirv::SPIRVTypeConverter`.  If the operation has a region, [signature
1365conversion][MlirDialectConversionSignatureConversion] might be needed as well.
1366
1367**Note**: The current validation rules of `spirv.module` require that all
1368operations contained within its region are valid operations in the SPIR-V
1369dialect.
1370
1371## Operation definitions
1372
1373[include "Dialects/SPIRVOps.md"]
1374
1375[Spirv]: https://www.khronos.org/registry/spir-v/
1376[SpirvSpec]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html
1377[SpirvLogicalLayout]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_logicallayout_a_logical_layout_of_a_module
1378[SpirvGrammar]: https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json
1379[SpirvShaderValidation]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_shadervalidation_a_validation_rules_for_shader_a_href_capability_capabilities_a
1380[SpirvOpVariable]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpVariable
1381[GlslStd450]: https://www.khronos.org/registry/spir-v/specs/1.0/GLSL.std.450.html
1382[ArrayType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeArray
1383[ImageType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeImage
1384[PointerType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypePointer
1385[RuntimeArrayType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeRuntimeArray
1386[SampledImageType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#OpTypeSampledImage
1387[MlirDialectConversion]: ../DialectConversion.md
1388[StructType]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#Structure
1389[SpirvTools]: https://github.com/KhronosGroup/SPIRV-Tools
1390[Rationale]: ../Rationale/Rationale.md/#block-arguments-vs-phi-nodes
1391[ODS]: ../DefiningDialects/Operations.md
1392[GreedyPatternRewriter]: https://github.com/llvm/llvm-project/blob/main/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp
1393[MlirDialectConversionTypeConversion]: ../DialectConversion.md/#type-converter
1394[MlirDialectConversionRewritePattern]: ../DialectConversion.md/#conversion-patterns
1395[MlirDialectConversionSignatureConversion]: ../DialectConversion.md/#region-signature-conversion
1396[MlirOpInterface]: ../Interfaces/#operation-interfaces
1397[MlirIntegerType]: Builtin.md/#integertype
1398[MlirVectorType]: Builtin.md/#vectortype
1399[MlirMemrefType]: Builtin.md/#memreftype
1400[MlirIndexType]: Builtin.md/#indextype
1401[MlirGpuDialect]: GPU.md
1402[MlirArithmeticDialect]: Arithmetic.md
1403[MlirSpirvHeaders]: https://github.com/llvm/llvm-project/tree/main/mlir/include/mlir/Dialect/SPIRV
1404[MlirSpirvLibs]: https://github.com/llvm/llvm-project/tree/main/mlir/lib/Dialect/SPIRV
1405[MlirSpirvTests]: https://github.com/llvm/llvm-project/tree/main/mlir/test/Dialect/SPIRV
1406[MlirSpirvUnittests]: https://github.com/llvm/llvm-project/tree/main/mlir/unittests/Dialect/SPIRV
1407[MlirGpuToSpirvHeaders]: https://github.com/llvm/llvm-project/tree/main/mlir/include/mlir/Conversion/GPUToSPIRV
1408[MlirGpuToSpirvLibs]: https://github.com/llvm/llvm-project/tree/main/mlir/lib/Conversion/GPUToSPIRV
1409[MlirFuncToSpirvHeaders]: https://github.com/llvm/llvm-project/tree/main/mlir/include/mlir/Conversion/FuncToSPIRV
1410[MlirFuncToSpirvLibs]: https://github.com/llvm/llvm-project/tree/main/mlir/lib/Conversion/FuncToSPIRV
1411[MlirSpirvDialect]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVDialect.h
1412[MlirSpirvTypes]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVTypes.h
1413[MlirSpirvOpsH]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVOps.h
1414[MlirSpirvSerialization]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Target/SPIRV/Serialization.h
1415[MlirSpirvBase]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
1416[MlirSpirvPasses]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/Transforms/Passes.h
1417[MlirSpirvConversion]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h
1418[MlirSpirvAbi]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/IR/TargetAndABI.h
1419[MlirSpirvOpsCpp]: https://github.com/llvm/llvm-project/blob/main/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
1420[GitHubDialectTracking]: https://github.com/tensorflow/mlir/issues/302
1421[GitHubLoweringTracking]: https://github.com/tensorflow/mlir/issues/303
1422[GenSpirvUtilsPy]: https://github.com/llvm/llvm-project/blob/main/mlir/utils/spirv/gen_spirv_dialect.py
1423[CustomTypeAttrTutorial]: ../DefiningDialects/AttributesAndTypes.md
1424[VulkanExtensionPhysicalStorageBuffer]: https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_physical_storage_buffer.html
1425[VulkanExtensionVariablePointers]: https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_variable_pointers.html
1426[VulkanSpirv]: https://renderdoc.org/vkspec_chunked/chap40.html#spirvenv
1427[VulkanShaderInterface]: https://renderdoc.org/vkspec_chunked/chap14.html#interfaces-resources
1428[VulkanShaderInterfaceStorageClass]: https://renderdoc.org/vkspec_chunked/chap15.html#interfaces
1429[VulkanResourceLimits]: https://renderdoc.org/vkspec_chunked/chap36.html#limits
1430[VulkanGPUInfoMaxPerStageDescriptorStorageBuffers]: https://vulkan.gpuinfo.org/displaydevicelimit.php?name=maxPerStageDescriptorStorageBuffers&platform=android
1431[VulkanPushConstants]: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdPushConstants.html
1432