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