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