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