1# SPIR-V Dialect to LLVM Dialect conversion manual 2 3This manual describes the conversion from [SPIR-V Dialect](Dialects/SPIR-V.md) 4to [LLVM Dialect](Dialects/LLVM.md). It assumes familiarity with both, and 5describes the design choices behind the modelling of SPIR-V concepts in LLVM 6Dialect. The conversion is an ongoing work, and is expected to grow as more 7features are implemented. 8 9Conversion can be performed by invoking an appropriate conversion pass: 10 11```shell 12mlir-opt -convert-spirv-to-llvm <filename.mlir> 13``` 14 15This pass performs type and operation conversions for SPIR-V operations as 16described in this document. 17 18[TOC] 19 20## Type Conversion 21 22This section describes how SPIR-V Dialect types are mapped to LLVM Dialect. 23 24### Scalar types 25 26SPIR-V Dialect | LLVM Dialect 27:------------: | :-----------------: 28`i<bitwidth>` | `!llvm.i<bitwidth>` 29`si<bitwidth>` | `!llvm.i<bitwidth>` 30`ui<bitwidth>` | `!llvm.i<bitwidth>` 31`f16` | `f16` 32`f32` | `f32` 33`f64` | `f64` 34 35### Vector types 36 37SPIR-V Dialect | LLVM Dialect 38:-------------------------------: | :-------------------------------: 39`vector<<count> x <scalar-type>>` | `vector<<count> x <scalar-type>>` 40 41### Pointer types 42 43A SPIR-V pointer also takes a Storage Class. At the moment, conversion does 44**not** take it into account. 45 46SPIR-V Dialect | LLVM Dialect 47:-------------------------------------------: | :-------------------------: 48`!spv.ptr< <element-type>, <storage-class> >` | `!llvm.ptr<<element-type>>` 49 50### Array types 51 52SPIR-V distinguishes between array type and run-time array type, the length of 53which is not known at compile time. In LLVM, it is possible to index beyond the 54end of the array. Therefore, runtime array can be implemented as a zero length 55array type. 56 57Moreover, SPIR-V supports the notion of array stride. Currently only natural 58strides (based on [`VulkanLayoutUtils`][VulkanLayoutUtils]) are supported. They 59are also mapped to LLVM array. 60 61SPIR-V Dialect | LLVM Dialect 62:-----------------------------------: | :-----------------------------------: 63`!spv.array<<count> x <element-type>>`| `!llvm.array<<count> x <element-type>>` 64`!spv.rtarray< <element-type> >` | `!llvm.array<0 x <element-type>>` 65 66### Struct types 67 68Members of SPIR-V struct types may have decorations and offset information. 69Currently, there is **no** support of member decorations conversion for structs. 70For more information see section on [Decorations](#Decorations-conversion). 71 72Usually we expect that each struct member has a natural size and alignment. 73However, there are cases (*e.g.* in graphics) where one would place struct 74members explicitly at particular offsets. This case is **not** supported 75at the moment. Hence, we adhere to the following mapping: 76 77* Structs with no offset are modelled as LLVM packed structures. 78 79* Structs with natural offset (*i.e.* offset that equals to cumulative size of 80 the previous struct elements or is a natural alignment) are mapped to 81 naturally padded structs. 82 83* Structs with unnatural offset (*i.e.* offset that is not equal to cumulative 84 size of the previous struct elements) are **not** supported. In this case, 85 offsets can be emulated with padding fields (*e.g.* integers). However, such 86 a design would require index recalculation in the conversion of ops that 87 involve memory addressing. 88 89Examples of SPIR-V struct conversion are: 90```mlir 91!spv.struct<i8, i32> => !llvm.struct<packed (i8, i32)> 92!spv.struct<i8 [0], i32 [4]> => !llvm.struct<(i8, i32)> 93 94// error 95!spv.struct<i8 [0], i32 [8]> 96``` 97 98### Not implemented types 99 100The rest of the types not mentioned explicitly above are not supported by the 101conversion. This includes `ImageType` and `MatrixType`. 102 103## Operation Conversion 104 105This section describes how SPIR-V Dialect operations are converted to LLVM 106Dialect. It lists already working conversion patterns, as well as those that are 107an ongoing work. 108 109There are also multiple ops for which there is no clear mapping in LLVM. 110Conversion for those have to be discussed within the community on the 111case-by-case basis. 112 113### Arithmetic ops 114 115SPIR-V arithmetic ops mostly have a direct equivalent in LLVM Dialect. Such 116exceptions as `spv.SMod` and `spv.FMod` are rare. 117 118SPIR-V Dialect op | LLVM Dialect op 119:-----------------------------------: | :-----------------------------------: 120`spv.FAdd` | `llvm.fadd` 121`spv.FDiv` | `llvm.fdiv` 122`spv.FNegate` | `llvm.fneg` 123`spv.FMul` | `llvm.fmul` 124`spv.FRem` | `llvm.frem` 125`spv.FSub` | `llvm.fsub` 126`spv.IAdd` | `llvm.add` 127`spv.IMul` | `llvm.mul` 128`spv.ISub` | `llvm.sub` 129`spv.SDiv` | `llvm.sdiv` 130`spv.SRem` | `llvm.srem` 131`spv.UDiv` | `llvm.udiv` 132`spv.UMod` | `llvm.urem` 133 134### Bitwise ops 135 136SPIR-V has a range of bit ops that are mapped to LLVM dialect ops, intrinsics or 137may have a specific conversion pattern. 138 139#### Direct conversion 140 141As with arithmetic ops, most of bitwise ops have a semantically equivalent op in 142LLVM: 143 144SPIR-V Dialect op | LLVM Dialect op 145:-----------------------------------: | :-----------------------------------: 146`spv.BitwiseAnd` | `llvm.and` 147`spv.BitwiseOr` | `llvm.or` 148`spv.BitwiseXor` | `llvm.xor` 149 150Also, some of bitwise ops can be modelled with LLVM intrinsics: 151 152SPIR-V Dialect op | LLVM Dialect intrinsic 153:-----------------------------------: | :-----------------------------------: 154`spv.BitCount` | `llvm.intr.ctpop` 155`spv.BitReverse` | `llvm.intr.bitreverse` 156 157#### `spv.Not` 158 159`spv.Not` is modelled with a `xor` operation with a mask with all bits set. 160 161```mlir 162 %mask = llvm.mlir.constant(-1 : i32) : i32 163%0 = spv.Not %op : i32 => %0 = llvm.xor %op, %mask : i32 164``` 165 166#### Bitfield ops 167 168SPIR-V dialect has three bitfield ops: `spv.BitFieldInsert`, 169`spv.BitFieldSExtract` and `spv.BitFieldUExtract`. This section will first 170outline the general design of conversion patterns for this ops, and then 171describe each of them. 172 173All of these ops take `base`, `offset` and `count` (`insert` for 174`spv.BitFieldInsert`) as arguments. There are two important things 175to note: 176 177* `offset` and `count` are always scalar. This means that we can have the 178 following case: 179 180 ```mlir 181 %0 = spv.BitFieldSExtract %base, %offset, %count : vector<2xi32>, i8, i8 182 ``` 183 184 To be able to proceed with conversion algorithms described below, all 185 operands have to be of the same type and bitwidth. This requires 186 broadcasting of `offset` and `count` to vectors, for example for the case 187 above it gives: 188 189 ```mlir 190 // Broadcasting offset 191 %offset0 = llvm.mlir.undef : vector<2xi8> 192 %zero = llvm.mlir.constant(0 : i32) : i32 193 %offset1 = llvm.insertelement %offset, %offset0[%zero : i32] : vector<2xi8> 194 %one = llvm.mlir.constant(1 : i32) : i32 195 %vec_offset = llvm.insertelement %offset, %offset1[%one : i32] : vector<2xi8> 196 197 // Broadcasting count 198 // ... 199 ``` 200 201* `offset` and `count` may have different bitwidths from `base`. In this case, 202 both of these operands have to be zero extended (since they are treated as 203 unsigned by the specification) or truncated. For the above example it would 204 be: 205 206 ```mlir 207 // Zero extending offset after broadcasting 208 %res_offset = llvm.zext %vec_offset: vector<2xi8> to vector<2xi32> 209 ``` 210 211 Also, note that if the bitwidth of `offset` or `count` is greater than the 212 bitwidth of `base`, truncation is still permitted. This is because the ops 213 have a defined behaviour with `offset` and `count` being less than the size 214 of `base`. It creates a natural upper bound on what values `offset` and 215 `count` can take, which is 64. This can be expressed in less than 8 bits. 216 217Now, having these two cases in mind, we can proceed with conversion for the ops 218and their operands. 219 220##### `spv.BitFieldInsert` 221 222This operation is implemented as a series of LLVM Dialect operations. First step 223would be to create a mask with bits set outside 224[`offset`, `offset` + `count` - 1]. Then, unchanged bits are extracted from 225`base` that are outside of [`offset`, `offset` + `count` - 1]. The result is 226`or`ed with shifted `insert`. 227 228```mlir 229// Create mask 230// %minus_one = llvm.mlir.constant(-1 : i32) : i32 231// %t0 = llvm.shl %minus_one, %count : i32 232// %t1 = llvm.xor %t0, %minus_one : i32 233// %t2 = llvm.shl %t1, %offset : i32 234// %mask = llvm.xor %t2, %minus_one : i32 235 236// Extract unchanged bits from the Base 237// %new_base = llvm.and %base, %mask : i32 238 239// Insert new bits 240// %sh_insert = llvm.shl %insert, %offset : i32 241// %res = llvm.or %new_base, %sh_insert : i32 242%res = spv.BitFieldInsert %base, %insert, %offset, %count : i32, i32, i32 243``` 244 245##### `spv.BitFieldSExtract` 246 247To implement `spv.BitFieldSExtract`, `base` is shifted left by [sizeof(`base`) - 248(`count` + `offset`)], so that the bit at `offset` + `count` - 1 is the most 249significant bit. After, the result is shifted right, filling the bits with the 250sign bit. 251 252```mlir 253// Calculate the amount to shift left. 254// %size = llvm.mlir.constant(32 : i32) : i32 255// %t0 = llvm.add %count, %offset : i32 256// %t1 = llvm.sub %size, %t0 : i32 257 258// Shift left and then right to extract the bits 259// %sh_left = llvm.shl %base, %t1 : i32 260// %t2 = llvm.add %offset, %t1 : i32 261// %res = llvm.ashr %sh_left, %t2 : i32 262%res = spv.BitFieldSExtract %base, %offset, %count : i32, i32, i32 263``` 264 265##### `spv.BitFieldUExtract` 266 267For this op a similar pattern as for `spv.BitFieldInsert` is used. First, a mask 268with bits set at [0, `count` - 1] is created. Then `base` is shifted by `offset` 269and the mask is applied. 270 271```mlir 272// Create a mask 273// %minus_one = llvm.mlir.constant(-1 : i32) : i32 274// %t0 = llvm.shl %minus_one, %count : i32 275// mask = llvm.xor %t0, %minus_one : i32 276 277// Shift Base and apply mask 278// %sh_base = llvm.lshr %base, %offset : i32 279// %res = llvm.and %sh_base, %mask : i32 280%res = spv.BitFieldUExtract %base, %offset, %count : i32, i32, i32 281``` 282 283### Cast ops 284 285#### Direct conversions 286 287SPIR-V Dialect op | LLVM Dialect op 288:-----------------------------------: | :-----------------------------------: 289`spv.ConvertFToS` | `llvm.fptosi` 290`spv.ConvertFToU` | `llvm.fptoui` 291`spv.ConvertSToF` | `llvm.sitofp` 292`spv.ConvertUToF` | `llvm.uitofp` 293 294#### spv.Bitcast 295This operation has a direct counterpart in LLVM: `llvm.bitcast`. It is treated 296separately since it also supports pointer to pointer bit pattern-preserving type 297conversion, apart from regular scalar or vector of numerical type. 298 299#### Special cases 300Special cases include `spv.FConvert`, `spv.SConvert` and `spv.UConvert`. These 301operations are either a truncate or extend. Let's denote the operand component 302width as A, and result component width as R. Then, the following mappings are 303used: 304 305##### `spv.FConvert` 306Case | LLVM Dialect op 307:-------------: | :-----------------------------------: 308A < R | `llvm.fpext` 309A > R | `llvm.fptrunc` 310 311##### `spv.SConvert` 312Case | LLVM Dialect op 313:-------------: | :-----------------------------------: 314A < R | `llvm.sext` 315A > R | `llvm.trunc` 316 317##### `spv.UConvert` 318Case | LLVM Dialect op 319:-------------: | :-----------------------------------: 320A < R | `llvm.zext` 321A > R | `llvm.trunc` 322 323The case when A = R is not possible, based on SPIR-V Dialect specification: 324> The component width cannot equal the component width in Result Type. 325 326### Comparison ops 327 328SPIR-V comparison ops are mapped to LLVM `icmp` and `fcmp` operations. 329 330SPIR-V Dialect op | LLVM Dialect op 331:-----------------------------------: | :-----------------------------------: 332`spv.IEqual` | `llvm.icmp "eq"` 333`spv.INotEqual` | `llvm.icmp "ne"` 334`spv.FOrdEqual` | `llvm.fcmp "oeq"` 335`spv.FOrdGreaterThan` | `llvm.fcmp "ogt"` 336`spv.FOrdGreaterThanEqual` | `llvm.fcmp "oge"` 337`spv.FOrdLessThan` | `llvm.fcmp "olt"` 338`spv.FOrdLessThanEqual` | `llvm.fcmp "ole"` 339`spv.FOrdNotEqual` | `llvm.fcmp "one"` 340`spv.FUnordEqual` | `llvm.fcmp "ueq"` 341`spv.FUnordGreaterThan` | `llvm.fcmp "ugt"` 342`spv.FUnordGreaterThanEqual` | `llvm.fcmp "uge"` 343`spv.FUnordLessThan` | `llvm.fcmp "ult"` 344`spv.FUnordLessThanEqual` | `llvm.fcmp "ule"` 345`spv.FUnordNotEqual` | `llvm.fcmp "une"` 346`spv.SGreaterThan` | `llvm.icmp "sgt"` 347`spv.SGreaterThanEqual` | `llvm.icmp "sge"` 348`spv.SLessThan` | `llvm.icmp "slt"` 349`spv.SLessThanEqual` | `llvm.icmp "sle"` 350`spv.UGreaterThan` | `llvm.icmp "ugt"` 351`spv.UGreaterThanEqual` | `llvm.icmp "uge"` 352`spv.ULessThan` | `llvm.icmp "ult"` 353`spv.ULessThanEqual` | `llvm.icmp "ule"` 354 355### Composite ops 356 357Currently, conversion supports rewrite patterns for `spv.CompositeExtract` and 358`spv.CompositeInsert`. We distinguish two cases for these operations: when the 359composite object is a vector, and when the composite object is of a non-vector 360type (*i.e.* struct, array or runtime array). 361 362Composite type | SPIR-V Dialect op | LLVM Dialect op 363:-------------: | :--------------------: | :--------------------: 364vector | `spv.CompositeExtract` | `llvm.extractelement` 365vector | `spv.CompositeInsert` | `llvm.insertelement` 366non-vector | `spv.CompositeExtract` | `llvm.extractvalue` 367non-vector | `spv.CompositeInsert` | `llvm.insertvalue` 368 369### `spv.EntryPoint` and `spv.ExecutionMode` 370 371First of all, it is important to note that there is no direct representation of 372entry points in LLVM. At the moment, we use the following approach: 373 374* `spv.EntryPoint` is simply removed. 375 376* In contrast, `spv.ExecutionMode` may contain important information about the 377 entry point. For example, `LocalSize` provides information about the 378 work-group size that can be reused. 379 380 In order to preserve this information, `spv.ExecutionMode` is converted to a 381 struct global variable that stores the execution mode id and any variables 382 associated with it. In C, the struct has the structure shown below. 383 384 ```C 385 // No values are associated // There are values that are associated 386 // with this entry point. // with this entry point. 387 struct { struct { 388 int32_t executionMode; int32_t executionMode; 389 }; int32_t values[]; 390 }; 391 ``` 392 393 ```mlir 394 // spv.ExecutionMode @empty "ContractionOff" 395 llvm.mlir.global external constant @{{.*}}() : !llvm.struct<(i32)> { 396 %0 = llvm.mlir.undef : !llvm.struct<(i32)> 397 %1 = llvm.mlir.constant(31 : i32) : i32 398 %ret = llvm.insertvalue %1, %0[0 : i32] : !llvm.struct<(i32)> 399 llvm.return %ret : !llvm.struct<(i32)> 400 } 401 ``` 402 403### Logical ops 404 405Logical ops follow a similar pattern as bitwise ops, with the difference that 406they operate on `i1` or vector of `i1` values. The following mapping is used to 407emulate SPIR-V ops behaviour: 408 409SPIR-V Dialect op | LLVM Dialect op 410:-----------------------------------: | :-----------------------------------: 411`spv.LogicalAnd` | `llvm.and` 412`spv.LogicalOr` | `llvm.or` 413`spv.LogicalEqual` | `llvm.icmp "eq"` 414`spv.LogicalNotEqual` | `llvm.icmp "ne"` 415 416`spv.LogicalNot` has the same conversion pattern as bitwise `spv.Not`. It is 417modelled with `xor` operation with a mask with all bits set. 418 419```mlir 420 %mask = llvm.mlir.constant(-1 : i1) : i1 421%0 = spv.LogicalNot %op : i1 => %0 = llvm.xor %op, %mask : i1 422``` 423 424### Memory ops 425 426This section describes the conversion patterns for SPIR-V dialect operations 427that concern memory. 428 429#### `spv.AccessChain` 430 431`spv.AccessChain` is mapped to `llvm.getelementptr` op. In order to create a 432valid LLVM op, we also add a 0 index to the `spv.AccessChain`'s indices list in 433order to go through the pointer. 434 435```mlir 436// Access the 1st element of the array 437%i = spv.Constant 1: i32 438%var = spv.Variable : !spv.ptr<!spv.struct<f32, !spv.array<4xf32>>, Function> 439%el = spv.AccessChain %var[%i, %i] : !spv.ptr<!spv.struct<f32, !spv.array<4xf32>>, Function>, i32, i32 440 441// Corresponding LLVM dialect code 442%i = ... 443%var = ... 444%0 = llvm.mlir.constant(0 : i32) : i32 445%el = llvm.getelementptr %var[%0, %i, %i] : (!llvm.ptr<struct<packed (f32, array<4 x f32>)>>, i32, i32, i32) 446``` 447 448#### `spv.Load` and `spv.Store` 449 450These ops are converted to their LLVM counterparts: `llvm.load` and 451`llvm.store`. If the op has a memory access attribute, then there are the 452following cases, based on the value of the attribute: 453 454* **Aligned**: alignment is passed on to LLVM op builder, for example: `mlir 455 // llvm.store %ptr, %val {alignment = 4 : i64} : !llvm.ptr<f32> spv.Store 456 "Function" %ptr, %val ["Aligned", 4] : f32` 457* **None**: same case as if there is no memory access attribute. 458 459* **Nontemporal**: set `nontemporal` flag, for example: `mlir // %res = 460 llvm.load %ptr {nontemporal} : !llvm.ptr<f32> %res = spv.Load "Function" 461 %ptr ["Nontemporal"] : f32` 462 463* **Volatile**: mark the op as `volatile`, for example: `mlir // %res = 464 llvm.load volatile %ptr : !llvm.ptr<f32> %res = spv.Load "Function" %ptr 465 ["Volatile"] : f32` Otherwise the conversion fails as other cases 466 (`MakePointerAvailable`, `MakePointerVisible`, `NonPrivatePointer`) are not 467 supported yet. 468 469#### `spv.GlobalVariable` and `spv.mlir.addressof` 470 471`spv.GlobalVariable` is modelled with `llvm.mlir.global` op. However, there 472is a difference that has to be pointed out. 473 474In SPIR-V dialect, the global variable returns a pointer, whereas in LLVM 475dialect the global holds an actual value. This difference is handled by 476`spv.mlir.addressof` and `llvm.mlir.addressof` ops that both return a pointer and 477are used to reference the global. 478 479```mlir 480// Original SPIR-V module 481spv.module Logical GLSL450 { 482 spv.GlobalVariable @struct : !spv.ptr<!spv.struct<f32, !spv.array<10xf32>>, Private> 483 spv.func @func() -> () "None" { 484 %0 = spv.mlir.addressof @struct : !spv.ptr<!spv.struct<f32, !spv.array<10xf32>>, Private> 485 spv.Return 486 } 487} 488 489// Converted result 490module { 491 llvm.mlir.global private @struct() : !llvm.struct<packed (f32, [10 x f32])> 492 llvm.func @func() { 493 %0 = llvm.mlir.addressof @struct : !llvm.ptr<struct<packed (f32, [10 x f32])>> 494 llvm.return 495 } 496} 497``` 498 499The SPIR-V to LLVM conversion does not involve modelling of workgroups. 500Hence, we say that only current invocation is in conversion's scope. This means 501that global variables with pointers of `Input`, `Output`, and `Private` storage 502classes are supported. Also, `StorageBuffer` storage class is allowed for 503executing [`mlir-spirv-cpu-runner`](#mlir-spirv-cpu-runner). 504 505Moreover, `bind` that specifies the descriptor set and the binding number and 506`built_in` that specifies SPIR-V `BuiltIn` decoration have no conversion into 507LLVM dialect. 508 509Currently `llvm.mlir.global`s are created with `private` linkage for `Private` 510storage class and `External` for other storage classes, based on SPIR-V spec: 511 512> By default, functions and global variables are private to a module and cannot 513be accessed by other modules. However, a module may be written to export or 514import functions and global (module scope) variables. 515 516If the global variable's pointer has `Input` storage class, then a `constant` 517flag is added to LLVM op: 518 519```mlir 520spv.GlobalVariable @var : !spv.ptr<f32, Input> => llvm.mlir.global external constant @var() : f32 521``` 522 523#### `spv.Variable` 524 525Per SPIR-V dialect spec, `spv.Variable` allocates an object in memory, resulting 526in a pointer to it, which can be used with `spv.Load` and `spv.Store`. It is 527also a function-level variable. 528 529`spv.Variable` is modelled as `llvm.alloca` op. If initialized, an additional 530store instruction is used. Note that there is no initialization for arrays and 531structs since constants of these types are not supported in LLVM dialect (TODO). 532Also, at the moment initialization is only possible via `spv.Constant`. 533 534```mlir 535// Conversion of VariableOp without initialization 536 %size = llvm.mlir.constant(1 : i32) : i32 537%res = spv.Variable : !spv.ptr<vector<3xf32>, Function> => %res = llvm.alloca %size x vector<3xf32> : (i32) -> !llvm.ptr<vec<3 x f32>> 538 539// Conversion of VariableOp with initialization 540 %c = llvm.mlir.constant(0 : i64) : i64 541%c = spv.Constant 0 : i64 %size = llvm.mlir.constant(1 : i32) : i32 542%res = spv.Variable init(%c) : !spv.ptr<i64, Function> => %res = llvm.alloca %[[SIZE]] x i64 : (i32) -> !llvm.ptr<i64> 543 llvm.store %c, %res : !llvm.ptr<i64> 544``` 545 546Note that simple conversion to `alloca` may not be sufficient if the code has 547some scoping. For example, if converting ops executed in a loop into `alloca`s, 548a stack overflow may occur. For this case, `stacksave`/`stackrestore` pair can 549be used (TODO). 550 551### Miscellaneous ops with direct conversions 552 553There are multiple SPIR-V ops that do not fit in a particular group but can be 554converted directly to LLVM dialect. Their conversion is addressed in this 555section. 556 557SPIR-V Dialect op | LLVM Dialect op 558:-----------------------------------: | :-----------------------------------: 559`spv.Select` | `llvm.select` 560`spv.Undef` | `llvm.mlir.undef` 561 562### Shift ops 563 564Shift operates on two operands: `shift` and `base`. 565 566In SPIR-V dialect, `shift` and `base` may have different bit width. On the 567contrary, in LLVM Dialect both `base` and `shift` have to be of the same 568bitwidth. This leads to the following conversions: 569 570* if `base` has the same bitwidth as `shift`, the conversion is 571 straightforward. 572 573* if `base` has a greater bit width than `shift`, shift is sign or zero 574 extended first. Then the extended value is passed to the shift. 575 576* otherwise, the conversion is considered to be illegal. 577 578```mlir 579// Shift without extension 580%res0 = spv.ShiftRightArithmetic %0, %2 : i32, i32 => %res0 = llvm.ashr %0, %2 : i32 581 582// Shift with extension 583 %ext = llvm.sext %1 : i16 to i32 584%res1 = spv.ShiftRightArithmetic %0, %1 : i32, i16 => %res1 = llvm.ashr %0, %ext: i32 585``` 586 587### `spv.Constant` 588 589At the moment `spv.Constant` conversion supports scalar and vector constants 590**only**. 591 592#### Mapping 593 594`spv.Constant` is mapped to `llvm.mlir.constant`. This is a straightforward 595conversion pattern with a special case when the argument is signed or unsigned. 596 597#### Special case 598 599SPIR-V constant can be a signed or unsigned integer. Since LLVM Dialect does not 600have signedness semantics, this case should be handled separately. 601 602The conversion casts constant value attribute to a signless integer or a vector 603of signless integers. This is correct because in SPIR-V, like in LLVM, how to 604interpret an integer number is also dictated by the opcode. However, in reality 605hardware implementation might show unexpected behavior. Therefore, it is better 606to handle it case-by-case, given that the purpose of the conversion is not to 607cover all possible corner cases. 608 609```mlir 610// %0 = llvm.mlir.constant(0 : i8) : i8 611%0 = spv.Constant 0 : i8 612 613// %1 = llvm.mlir.constant(dense<[2, 3, 4]> : vector<3xi32>) : vector<3xi32> 614%1 = spv.Constant dense<[2, 3, 4]> : vector<3xui32> 615``` 616 617### Not implemented ops 618 619There is no support of the following ops: 620 621* All atomic ops 622* All group ops 623* All matrix ops 624* All OCL ops 625 626As well as: 627 628* spv.CompositeConstruct 629* spv.ControlBarrier 630* spv.CopyMemory 631* spv.FMod 632* spv.GLSL.Acos 633* spv.GLSL.Asin 634* spv.GLSL.Atan 635* spv.GLSL.Cosh 636* spv.GLSL.FSign 637* spv.GLSL.SAbs 638* spv.GLSL.Sinh 639* spv.GLSL.SSign 640* spv.MemoryBarrier 641* spv.mlir.referenceof 642* spv.SMod 643* spv.SpecConstant 644* spv.Unreachable 645* spv.VectorExtractDynamic 646 647## Control flow conversion 648 649### Branch ops 650 651`spv.Branch` and `spv.BranchConditional` are mapped to `llvm.br` and 652`llvm.cond_br`. Branch weights for `spv.BranchConditional` are mapped to 653corresponding `branch_weights` attribute of `llvm.cond_br`. When translated to 654proper LLVM, `branch_weights` are converted into LLVM metadata associated with 655the conditional branch. 656 657### `spv.FunctionCall` 658 659`spv.FunctionCall` maps to `llvm.call`. For example: 660 661```mlir 662%0 = spv.FunctionCall @foo() : () -> i32 => %0 = llvm.call @foo() : () -> f32 663spv.FunctionCall @bar(%0) : (i32) -> () => llvm.call @bar(%0) : (f32) -> () 664``` 665 666### `spv.mlir.selection` and `spv.mlir.loop` 667 668Control flow within `spv.mlir.selection` and `spv.mlir.loop` is lowered directly to LLVM 669via branch ops. The conversion can only be applied to selection or loop with all 670blocks being reachable. Moreover, selection and loop control attributes (such as 671`Flatten` or `Unroll`) are not supported at the moment. 672 673```mlir 674// Conversion of selection 675%cond = spv.Constant true %cond = llvm.mlir.constant(true) : i1 676spv.mlir.selection { 677 spv.BranchConditional %cond, ^true, ^false llvm.cond_br %cond, ^true, ^false 678 679^true: ^true: 680 // True block code // True block code 681 spv.Branch ^merge => llvm.br ^merge 682 683^false: ^false: 684 // False block code // False block code 685 spv.Branch ^merge llvm.br ^merge 686 687^merge: ^merge: 688 spv.mlir.merge llvm.br ^continue 689} 690// Remaining code ^continue: 691 // Remaining code 692``` 693 694```mlir 695// Conversion of loop 696%cond = spv.Constant true %cond = llvm.mlir.constant(true) : i1 697spv.mlir.loop { 698 spv.Branch ^header llvm.br ^header 699 700^header: ^header: 701 // Header code // Header code 702 spv.BranchConditional %cond, ^body, ^merge => llvm.cond_br %cond, ^body, ^merge 703 704^body: ^body: 705 // Body code // Body code 706 spv.Branch ^continue llvm.br ^continue 707 708^continue: ^continue: 709 // Continue code // Continue code 710 spv.Branch ^header llvm.br ^header 711 712^merge: ^merge: 713 spv.mlir.merge llvm.br ^remaining 714} 715// Remaining code ^remaining: 716 // Remaining code 717``` 718 719## Decorations conversion 720 721**Note: these conversions have not been implemented yet** 722 723## GLSL extended instruction set 724 725This section describes how SPIR-V ops from GLSL extended instructions set are 726mapped to LLVM Dialect. 727 728### Direct conversions 729 730SPIR-V Dialect op | LLVM Dialect op 731:-----------------------------------: | :-----------------------------------: 732`spv.GLSL.Ceil` | `llvm.intr.ceil` 733`spv.GLSL.Cos` | `llvm.intr.cos` 734`spv.GLSL.Exp` | `llvm.intr.exp` 735`spv.GLSL.FAbs` | `llvm.intr.fabs` 736`spv.GLSL.Floor` | `llvm.intr.floor` 737`spv.GLSL.FMax` | `llvm.intr.maxnum` 738`spv.GLSL.FMin` | `llvm.intr.minnum` 739`spv.GLSL.Log` | `llvm.intr.log` 740`spv.GLSL.Sin` | `llvm.intr.sin` 741`spv.GLSL.Sqrt` | `llvm.intr.sqrt` 742`spv.GLSL.SMax` | `llvm.intr.smax` 743`spv.GLSL.SMin` | `llvm.intr.smin` 744 745### Special cases 746 747`spv.InverseSqrt` is mapped to: 748 749```mlir 750 %one = llvm.mlir.constant(1.0 : f32) : f32 751%res = spv.InverseSqrt %arg : f32 => %sqrt = "llvm.intr.sqrt"(%arg) : (f32) -> f32 752 %res = fdiv %one, %sqrt : f32 753``` 754 755`spv.Tan` is mapped to: 756 757```mlir 758 %sin = "llvm.intr.sin"(%arg) : (f32) -> f32 759%res = spv.Tan %arg : f32 => %cos = "llvm.intr.cos"(%arg) : (f32) -> f32 760 %res = fdiv %sin, %cos : f32 761``` 762 763`spv.Tanh` is modelled using the equality `tanh(x) = {exp(2x) - 1}/{exp(2x) + 1}`: 764 765```mlir 766 %two = llvm.mlir.constant(2.0: f32) : f32 767 %2xArg = llvm.fmul %two, %arg : f32 768 %exp = "llvm.intr.exp"(%2xArg) : (f32) -> f32 769%res = spv.Tanh %arg : f32 => %one = llvm.mlir.constant(1.0 : f32) : f32 770 %num = llvm.fsub %exp, %one : f32 771 %den = llvm.fadd %exp, %one : f32 772 %res = llvm.fdiv %num, %den : f32 773``` 774 775## Function conversion and related ops 776 777This section describes the conversion of function-related operations from SPIR-V 778to LLVM dialect. 779 780### `spv.func` 781This op declares or defines a SPIR-V function and it is converted to `llvm.func`. 782This conversion handles signature conversion, and function control attributes 783remapping to LLVM dialect function [`passthrough` attribute](Dialects/LLVM.md/#attribute-pass-through). 784 785The following mapping is used to map [SPIR-V function control][SPIRVFunctionAttributes] to 786[LLVM function attributes][LLVMFunctionAttributes]: 787 788SPIR-V Function Control Attributes | LLVM Function Attributes 789:-----------------------------------: | :-----------------------------------: 790None | No function attributes passed 791Inline | `alwaysinline` 792DontInline | `noinline` 793Pure | `readonly` 794Const | `readnone` 795 796### `spv.Return` and `spv.ReturnValue` 797 798In LLVM IR, functions may return either 1 or 0 value. Hence, we map both ops to 799`llvm.return` with or without a return value. 800 801## Module ops 802 803Module in SPIR-V has one region that contains one block. It is defined via 804`spv.module` op that also takes a range of attributes: 805 806* Addressing model 807* Memory model 808* Version-Capability-Extension attribute 809 810`spv.module` is converted into `ModuleOp`. This plays a role of enclosing scope 811to LLVM ops. At the moment, SPIR-V module attributes are ignored. 812 813## `mlir-spirv-cpu-runner` 814 815`mlir-spirv-cpu-runner` allows to execute `gpu` dialect kernel on the CPU via 816SPIR-V to LLVM dialect conversion. Currently, only single-threaded kernel is 817supported. 818 819To build the runner, add the following option to `cmake`: 820```bash 821-DMLIR_ENABLE_SPIRV_CPU_RUNNER=1 822``` 823 824### Pipeline 825 826The `gpu` module with the kernel and the host code undergo the following 827transformations: 828 829* Convert the `gpu` module into SPIR-V dialect, lower ABI attributes and 830 update version, capability and extension. 831 832* Emulate the kernel call by converting the launching operation into a normal 833 function call. The data from the host side to the device is passed via 834 copying to global variables. These are created in both the host and the 835 kernel code and later linked when nested modules are folded. 836 837* Convert SPIR-V dialect kernel to LLVM dialect via the new conversion path. 838 839After these passes, the IR transforms into a nested LLVM module - a main module 840representing the host code and a kernel module. These modules are linked and 841executed using `ExecutionEngine`. 842 843### Walk-through 844 845This section gives a detailed overview of the IR changes while running 846`mlir-spirv-cpu-runner`. First, consider that we have the following IR. (For 847simplicity some type annotations and function implementations have been 848omitted). 849 850```mlir 851gpu.module @foo { 852 gpu.func @bar(%arg: memref<8xi32>) { 853 // Kernel code. 854 gpu.return 855 } 856} 857 858func @main() { 859 // Fill the buffer with some data 860 %buffer = alloc : memref<8xi32> 861 %data = ... 862 call fillBuffer(%buffer, %data) 863 864 "gpu.launch_func"(/*grid dimensions*/, %buffer) { 865 kernel = @foo::bar 866 } 867} 868``` 869 870Lowering `gpu` dialect to SPIR-V dialect results in 871 872```mlir 873spv.module @__spv__foo /*VCE triple and other metadata here*/ { 874 spv.GlobalVariable @__spv__foo_arg bind(0,0) : ... 875 spv.func @bar() { 876 // Kernel code. 877 } 878 spv.EntryPoint @bar, ... 879} 880 881func @main() { 882 // Fill the buffer with some data. 883 %buffer = alloc : memref<8xi32> 884 %data = ... 885 call fillBuffer(%buffer, %data) 886 887 "gpu.launch_func"(/*grid dimensions*/, %buffer) { 888 kernel = @foo::bar 889 } 890} 891``` 892 893Then, the lowering from standard dialect to LLVM dialect is applied to the host 894code. 895 896```mlir 897spv.module @__spv__foo /*VCE triple and other metadata here*/ { 898 spv.GlobalVariable @__spv__foo_arg bind(0,0) : ... 899 spv.func @bar() { 900 // Kernel code. 901 } 902 spv.EntryPoint @bar, ... 903} 904 905// Kernel function declaration. 906llvm.func @__spv__foo_bar() : ... 907 908llvm.func @main() { 909 // Fill the buffer with some data. 910 llvm.call fillBuffer(%buffer, %data) 911 912 // Copy data to the global variable, call kernel, and copy the data back. 913 %addr = llvm.mlir.addressof @__spv__foo_arg_descriptor_set0_binding0 : ... 914 "llvm.intr.memcpy"(%addr, %buffer) : ... 915 llvm.call @__spv__foo_bar() 916 "llvm.intr.memcpy"(%buffer, %addr) : ... 917 918 llvm.return 919} 920``` 921 922Finally, SPIR-V module is converted to LLVM and the symbol names are resolved 923for the linkage. 924 925```mlir 926module @__spv__foo { 927 llvm.mlir.global @__spv__foo_arg_descriptor_set0_binding0 : ... 928 llvm.func @__spv__foo_bar() { 929 // Kernel code. 930 } 931} 932 933// Kernel function declaration. 934llvm.func @__spv__foo_bar() : ... 935 936llvm.func @main() { 937 // Fill the buffer with some data. 938 llvm.call fillBuffer(%buffer, %data) 939 940 // Copy data to the global variable, call kernel, and copy the data back. 941 %addr = llvm.mlir.addressof @__spv__foo_arg_descriptor_set0_binding0 : ... 942 "llvm.intr.memcpy"(%addr, %buffer) : ... 943 llvm.call @__spv__foo_bar() 944 "llvm.intr.memcpy"(%buffer, %addr) : ... 945 946 llvm.return 947} 948``` 949 950[LLVMFunctionAttributes]: https://llvm.org/docs/LangRef.html#function-attributes 951[SPIRVFunctionAttributes]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_function_control_a_function_control 952[VulkanLayoutUtils]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h 953