1 # SPIR-V Dialect to LLVM Dialect conversion manual
3 This manual describes the conversion from [SPIR-V Dialect](Dialects/SPIR-V.md)
4 to [LLVM Dialect](Dialects/LLVM.md). It assumes familiarity with both, and
5 describes the design choices behind the modelling of SPIR-V concepts in LLVM
6 Dialect. The conversion is an ongoing work, and is expected to grow as more
7 features are implemented.
9 Conversion can be performed by invoking an appropriate conversion pass:
12 mlir-opt -convert-spirv-to-llvm <filename.mlir>
15 This pass performs type and operation conversions for SPIR-V operations as
16 described in this document.
22 This section describes how SPIR-V Dialect types are mapped to LLVM Dialect.
26 SPIR-V Dialect | LLVM Dialect
27 :------------: | :-----------------:
28 `i<bitwidth>` | `!llvm.i<bitwidth>`
29 `si<bitwidth>` | `!llvm.i<bitwidth>`
30 `ui<bitwidth>` | `!llvm.i<bitwidth>`
37 SPIR-V Dialect | LLVM Dialect
38 :-------------------------------: | :-------------------------------:
39 `vector<<count> x <scalar-type>>` | `vector<<count> x <scalar-type>>`
43 A SPIR-V pointer also takes a Storage Class. At the moment, conversion does
44 **not** take it into account.
46 SPIR-V Dialect | LLVM Dialect
47 :-------------------------------------------: | :-------------------------:
48 `!spirv.ptr< <element-type>, <storage-class> >` | `!llvm.ptr`
52 SPIR-V distinguishes between array type and run-time array type, the length of
53 which is not known at compile time. In LLVM, it is possible to index beyond the
54 end of the array. Therefore, runtime array can be implemented as a zero length
57 Moreover, SPIR-V supports the notion of array stride. Currently only natural
58 strides (based on [`VulkanLayoutUtils`][VulkanLayoutUtils]) are supported. They
59 are also mapped to LLVM array.
61 SPIR-V Dialect | LLVM Dialect
62 :------------------------------------: | :-------------------------------------:
63 `!spirv.array<<count> x <element-type>>` | `!llvm.array<<count> x <element-type>>`
64 `!spirv.rtarray< <element-type> >` | `!llvm.array<0 x <element-type>>`
68 Members of SPIR-V struct types may have decorations and offset information.
69 Currently, there is **no** support of member decorations conversion for structs.
70 For more information see section on [Decorations](#Decorations-conversion).
72 Usually we expect that each struct member has a natural size and alignment.
73 However, there are cases (*e.g.* in graphics) where one would place struct
74 members explicitly at particular offsets. This case is **not** supported at the
75 moment. Hence, we adhere to the following mapping:
77 * Structs with no offset are modelled as LLVM packed structures.
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.
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.
89 Examples of SPIR-V struct conversion are: ```mlir !spirv.struct<i8, i32> =>
90 !llvm.struct<packed (i8, i32)> !spirv.struct<i8 [0], i32 [4]> => !llvm.struct<(i8,
93 // error !spirv.struct<i8 [0], i32 [8]> ```
95 ### Not implemented types
97 The rest of the types not mentioned explicitly above are not supported by the
98 conversion. This includes `ImageType` and `MatrixType`.
100 ## Operation Conversion
102 This section describes how SPIR-V Dialect operations are converted to LLVM
103 Dialect. It lists already working conversion patterns, as well as those that are
106 There are also multiple ops for which there is no clear mapping in LLVM.
107 Conversion for those have to be discussed within the community on the
112 SPIR-V arithmetic ops mostly have a direct equivalent in LLVM Dialect. Such
113 exceptions as `spirv.SMod` and `spirv.FMod` are rare.
115 SPIR-V Dialect op | LLVM Dialect op
116 :---------------: | :-------------:
117 `spirv.FAdd` | `llvm.fadd`
118 `spirv.FDiv` | `llvm.fdiv`
119 `spirv.FNegate` | `llvm.fneg`
120 `spirv.FMul` | `llvm.fmul`
121 `spirv.FRem` | `llvm.frem`
122 `spirv.FSub` | `llvm.fsub`
123 `spirv.IAdd` | `llvm.add`
124 `spirv.IMul` | `llvm.mul`
125 `spirv.ISub` | `llvm.sub`
126 `spirv.SDiv` | `llvm.sdiv`
127 `spirv.SRem` | `llvm.srem`
128 `spirv.UDiv` | `llvm.udiv`
129 `spirv.UMod` | `llvm.urem`
133 SPIR-V has a range of bit ops that are mapped to LLVM dialect ops, intrinsics or
134 may have a specific conversion pattern.
136 #### Direct conversion
138 As with arithmetic ops, most of bitwise ops have a semantically equivalent op in
141 SPIR-V Dialect op | LLVM Dialect op
142 :---------------: | :-------------:
143 `spirv.BitwiseAnd` | `llvm.and`
144 `spirv.BitwiseOr` | `llvm.or`
145 `spirv.BitwiseXor` | `llvm.xor`
147 Also, some of bitwise ops can be modelled with LLVM intrinsics:
149 SPIR-V Dialect op | LLVM Dialect intrinsic
150 :---------------: | :--------------------:
151 `spirv.BitCount` | `llvm.intr.ctpop`
152 `spirv.BitReverse` | `llvm.intr.bitreverse`
156 `spirv.Not` is modelled with a `xor` operation with a mask with all bits set.
159 %mask = llvm.mlir.constant(-1 : i32) : i32
160 %0 = spirv.Not %op : i32 => %0 = llvm.xor %op, %mask : i32
165 SPIR-V dialect has three bitfield ops: `spirv.BitFieldInsert`,
166 `spirv.BitFieldSExtract` and `spirv.BitFieldUExtract`. This section will first
167 outline the general design of conversion patterns for this ops, and then
168 describe each of them.
170 All of these ops take `base`, `offset` and `count` (`insert` for
171 `spirv.BitFieldInsert`) as arguments. There are two important things to note:
173 * `offset` and `count` are always scalar. This means that we can have the
177 %0 = spirv.BitFieldSExtract %base, %offset, %count : vector<2xi32>, i8, i8
180 To be able to proceed with conversion algorithms described below, all
181 operands have to be of the same type and bitwidth. This requires
182 broadcasting of `offset` and `count` to vectors, for example for the case
186 // Broadcasting offset
187 %offset0 = llvm.mlir.undef : vector<2xi8>
188 %zero = llvm.mlir.constant(0 : i32) : i32
189 %offset1 = llvm.insertelement %offset, %offset0[%zero : i32] : vector<2xi8>
190 %one = llvm.mlir.constant(1 : i32) : i32
191 %vec_offset = llvm.insertelement %offset, %offset1[%one : i32] : vector<2xi8>
193 // Broadcasting count
197 * `offset` and `count` may have different bitwidths from `base`. In this case,
198 both of these operands have to be zero extended (since they are treated as
199 unsigned by the specification) or truncated. For the above example it would
203 // Zero extending offset after broadcasting
204 %res_offset = llvm.zext %vec_offset: vector<2xi8> to vector<2xi32>
207 Also, note that if the bitwidth of `offset` or `count` is greater than the
208 bitwidth of `base`, truncation is still permitted. This is because the ops
209 have a defined behaviour with `offset` and `count` being less than the size
210 of `base`. It creates a natural upper bound on what values `offset` and
211 `count` can take, which is 64. This can be expressed in less than 8 bits.
213 Now, having these two cases in mind, we can proceed with conversion for the ops
216 ##### `spirv.BitFieldInsert`
218 This operation is implemented as a series of LLVM Dialect operations. First step
219 would be to create a mask with bits set outside [`offset`, `offset` + `count` -
220 1]. Then, unchanged bits are extracted from `base` that are outside of
221 [`offset`, `offset` + `count` - 1]. The result is `or`ed with shifted `insert`.
225 // %minus_one = llvm.mlir.constant(-1 : i32) : i32
226 // %t0 = llvm.shl %minus_one, %count : i32
227 // %t1 = llvm.xor %t0, %minus_one : i32
228 // %t2 = llvm.shl %t1, %offset : i32
229 // %mask = llvm.xor %t2, %minus_one : i32
231 // Extract unchanged bits from the Base
232 // %new_base = llvm.and %base, %mask : i32
235 // %sh_insert = llvm.shl %insert, %offset : i32
236 // %res = llvm.or %new_base, %sh_insert : i32
237 %res = spirv.BitFieldInsert %base, %insert, %offset, %count : i32, i32, i32
240 ##### `spirv.BitFieldSExtract`
242 To implement `spirv.BitFieldSExtract`, `base` is shifted left by [sizeof(`base`) -
243 (`count` + `offset`)], so that the bit at `offset` + `count` - 1 is the most
244 significant bit. After, the result is shifted right, filling the bits with the
248 // Calculate the amount to shift left.
249 // %size = llvm.mlir.constant(32 : i32) : i32
250 // %t0 = llvm.add %count, %offset : i32
251 // %t1 = llvm.sub %size, %t0 : i32
253 // Shift left and then right to extract the bits
254 // %sh_left = llvm.shl %base, %t1 : i32
255 // %t2 = llvm.add %offset, %t1 : i32
256 // %res = llvm.ashr %sh_left, %t2 : i32
257 %res = spirv.BitFieldSExtract %base, %offset, %count : i32, i32, i32
260 ##### `spirv.BitFieldUExtract`
262 For this op a similar pattern as for `spirv.BitFieldInsert` is used. First, a mask
263 with bits set at [0, `count` - 1] is created. Then `base` is shifted by `offset`
264 and the mask is applied.
268 // %minus_one = llvm.mlir.constant(-1 : i32) : i32
269 // %t0 = llvm.shl %minus_one, %count : i32
270 // mask = llvm.xor %t0, %minus_one : i32
272 // Shift Base and apply mask
273 // %sh_base = llvm.lshr %base, %offset : i32
274 // %res = llvm.and %sh_base, %mask : i32
275 %res = spirv.BitFieldUExtract %base, %offset, %count : i32, i32, i32
280 #### Direct conversions
282 SPIR-V Dialect op | LLVM Dialect op
283 :---------------: | :-------------:
284 `spirv.ConvertFToS` | `llvm.fptosi`
285 `spirv.ConvertFToU` | `llvm.fptoui`
286 `spirv.ConvertSToF` | `llvm.sitofp`
287 `spirv.ConvertUToF` | `llvm.uitofp`
291 This operation has a direct counterpart in LLVM: `llvm.bitcast`. It is treated
292 separately since it also supports pointer to pointer bit pattern-preserving type
293 conversion, apart from regular scalar or vector of numerical type.
297 Special cases include `spirv.FConvert`, `spirv.SConvert` and `spirv.UConvert`. These
298 operations are either a truncate or extend. Let's denote the operand component
299 width as A, and result component width as R. Then, the following mappings are
302 ##### `spirv.FConvert`
304 Case | LLVM Dialect op
305 :---: | :-------------:
307 A > R | `llvm.fptrunc`
309 ##### `spirv.SConvert`
311 Case | LLVM Dialect op
312 :---: | :-------------:
316 ##### `spirv.UConvert`
318 Case | LLVM Dialect op
319 :---: | :-------------:
323 The case when A = R is not possible, based on SPIR-V Dialect specification:
325 > The component width cannot equal the component width in Result Type.
329 SPIR-V comparison ops are mapped to LLVM `icmp` and `fcmp` operations.
331 SPIR-V Dialect op | LLVM Dialect op
332 :--------------------------: | :---------------:
333 `spirv.IEqual` | `llvm.icmp "eq"`
334 `spirv.INotEqual` | `llvm.icmp "ne"`
335 `spirv.FOrdEqual` | `llvm.fcmp "oeq"`
336 `spirv.FOrdGreaterThan` | `llvm.fcmp "ogt"`
337 `spirv.FOrdGreaterThanEqual` | `llvm.fcmp "oge"`
338 `spirv.FOrdLessThan` | `llvm.fcmp "olt"`
339 `spirv.FOrdLessThanEqual` | `llvm.fcmp "ole"`
340 `spirv.FOrdNotEqual` | `llvm.fcmp "one"`
341 `spirv.FUnordEqual` | `llvm.fcmp "ueq"`
342 `spirv.FUnordGreaterThan` | `llvm.fcmp "ugt"`
343 `spirv.FUnordGreaterThanEqual` | `llvm.fcmp "uge"`
344 `spirv.FUnordLessThan` | `llvm.fcmp "ult"`
345 `spirv.FUnordLessThanEqual` | `llvm.fcmp "ule"`
346 `spirv.FUnordNotEqual` | `llvm.fcmp "une"`
347 `spirv.SGreaterThan` | `llvm.icmp "sgt"`
348 `spirv.SGreaterThanEqual` | `llvm.icmp "sge"`
349 `spirv.SLessThan` | `llvm.icmp "slt"`
350 `spirv.SLessThanEqual` | `llvm.icmp "sle"`
351 `spirv.UGreaterThan` | `llvm.icmp "ugt"`
352 `spirv.UGreaterThanEqual` | `llvm.icmp "uge"`
353 `spirv.ULessThan` | `llvm.icmp "ult"`
354 `spirv.ULessThanEqual` | `llvm.icmp "ule"`
358 Currently, conversion supports rewrite patterns for `spirv.CompositeExtract` and
359 `spirv.CompositeInsert`. We distinguish two cases for these operations: when the
360 composite object is a vector, and when the composite object is of a non-vector
361 type (*i.e.* struct, array or runtime array).
363 Composite type | SPIR-V Dialect op | LLVM Dialect op
364 :------------: | :--------------------: | :-------------------:
365 vector | `spirv.CompositeExtract` | `llvm.extractelement`
366 vector | `spirv.CompositeInsert` | `llvm.insertelement`
367 non-vector | `spirv.CompositeExtract` | `llvm.extractvalue`
368 non-vector | `spirv.CompositeInsert` | `llvm.insertvalue`
370 ### `spirv.EntryPoint` and `spirv.ExecutionMode`
372 First of all, it is important to note that there is no direct representation of
373 entry points in LLVM. At the moment, we use the following approach:
375 * `spirv.EntryPoint` is simply removed.
377 * In contrast, `spirv.ExecutionMode` may contain important information about the
378 entry point. For example, `LocalSize` provides information about the
379 work-group size that can be reused.
381 In order to preserve this information, `spirv.ExecutionMode` is converted to a
382 struct global variable that stores the execution mode id and any variables
383 associated with it. In C, the struct has the structure shown below.
386 // No values are associated // There are values that are associated
387 // with this entry point. // with this entry point.
389 int32_t executionMode; int32_t executionMode;
395 // spirv.ExecutionMode @empty "ContractionOff"
396 llvm.mlir.global external constant @{{.*}}() : !llvm.struct<(i32)> {
397 %0 = llvm.mlir.undef : !llvm.struct<(i32)>
398 %1 = llvm.mlir.constant(31 : i32) : i32
399 %ret = llvm.insertvalue %1, %0[0] : !llvm.struct<(i32)>
400 llvm.return %ret : !llvm.struct<(i32)>
406 Logical ops follow a similar pattern as bitwise ops, with the difference that
407 they operate on `i1` or vector of `i1` values. The following mapping is used to
408 emulate SPIR-V ops behaviour:
410 SPIR-V Dialect op | LLVM Dialect op
411 :-------------------: | :--------------:
412 `spirv.LogicalAnd` | `llvm.and`
413 `spirv.LogicalOr` | `llvm.or`
414 `spirv.LogicalEqual` | `llvm.icmp "eq"`
415 `spirv.LogicalNotEqual` | `llvm.icmp "ne"`
417 `spirv.LogicalNot` has the same conversion pattern as bitwise `spirv.Not`. It is
418 modelled with `xor` operation with a mask with all bits set.
421 %mask = llvm.mlir.constant(-1 : i1) : i1
422 %0 = spirv.LogicalNot %op : i1 => %0 = llvm.xor %op, %mask : i1
427 This section describes the conversion patterns for SPIR-V dialect operations
430 #### `spirv.AccessChain`
432 `spirv.AccessChain` is mapped to `llvm.getelementptr` op. In order to create a
433 valid LLVM op, we also add a 0 index to the `spirv.AccessChain`'s indices list in
434 order to go through the pointer.
437 // Access the 1st element of the array
438 %i = spirv.Constant 1: i32
439 %var = spirv.Variable : !spirv.ptr<!spirv.struct<f32, !spirv.array<4xf32>>, Function>
440 %el = spirv.AccessChain %var[%i, %i] : !spirv.ptr<!spirv.struct<f32, !spirv.array<4xf32>>, Function>, i32, i32
442 // Corresponding LLVM dialect code
445 %0 = llvm.mlir.constant(0 : i32) : i32
446 %el = llvm.getelementptr %var[%0, %i, %i] : (!llvm.ptr, i32, i32, i32), !llvm.struct<packed (f32, array<4 x f32>)>
449 #### `spirv.Load` and `spirv.Store`
451 These ops are converted to their LLVM counterparts: `llvm.load` and
452 `llvm.store`. If the op has a memory access attribute, then there are the
453 following cases, based on the value of the attribute:
455 * **Aligned**: alignment is passed on to LLVM op builder, for example: `mlir
456 // llvm.store %ptr, %val {alignment = 4 : i64} : !llvm.ptr spirv.Store
457 "Function" %ptr, %val ["Aligned", 4] : f32`
458 * **None**: same case as if there is no memory access attribute.
460 * **Nontemporal**: set `nontemporal` flag, for example: `mlir // %res =
461 llvm.load %ptr {nontemporal} : !llvm.ptr %res = spirv.Load "Function"
462 %ptr ["Nontemporal"] : f32`
464 * **Volatile**: mark the op as `volatile`, for example: `mlir // %res =
465 llvm.load volatile %ptr : !llvm.ptr f32> %res = spirv.Load "Function" %ptr
466 ["Volatile"] : f32` Otherwise the conversion fails as other cases
467 (`MakePointerAvailable`, `MakePointerVisible`, `NonPrivatePointer`) are not
470 #### `spirv.GlobalVariable` and `spirv.mlir.addressof`
472 `spirv.GlobalVariable` is modelled with `llvm.mlir.global` op. However, there is a
473 difference that has to be pointed out.
475 In SPIR-V dialect, the global variable returns a pointer, whereas in LLVM
476 dialect the global holds an actual value. This difference is handled by
477 `spirv.mlir.addressof` and `llvm.mlir.addressof` ops that both return a pointer
478 and are used to reference the global.
481 // Original SPIR-V module
482 spirv.module Logical GLSL450 {
483 spirv.GlobalVariable @struct : !spirv.ptr<!spirv.struct<f32, !spirv.array<10xf32>>, Private>
484 spirv.func @func() -> () "None" {
485 %0 = spirv.mlir.addressof @struct : !spirv.ptr<!spirv.struct<f32, !spirv.array<10xf32>>, Private>
492 llvm.mlir.global private @struct() : !llvm.struct<packed (f32, [10 x f32])>
494 %0 = llvm.mlir.addressof @struct : !llvm.ptr
500 The SPIR-V to LLVM conversion does not involve modelling of workgroups. Hence,
501 we say that only current invocation is in conversion's scope. This means that
502 global variables with pointers of `Input`, `Output`, and `Private` storage
503 classes are supported. Also, `StorageBuffer` storage class is allowed for
504 executing [SPIR-V CPU Runner tests](#spir-v-cpu-runner-tests).
506 Moreover, `bind` that specifies the descriptor set and the binding number and
507 `built_in` that specifies SPIR-V `BuiltIn` decoration have no conversion into
510 Currently `llvm.mlir.global`s are created with `private` linkage for `Private`
511 storage class and `External` for other storage classes, based on SPIR-V spec:
513 > By default, functions and global variables are private to a module and cannot
514 > be accessed by other modules. However, a module may be written to export or
515 > import functions and global (module scope) variables.
517 If the global variable's pointer has `Input` storage class, then a `constant`
518 flag is added to LLVM op:
521 spirv.GlobalVariable @var : !spirv.ptr<f32, Input> => llvm.mlir.global external constant @var() : f32
524 #### `spirv.Variable`
526 Per SPIR-V dialect spec, `spirv.Variable` allocates an object in memory, resulting
527 in a pointer to it, which can be used with `spirv.Load` and `spirv.Store`. It is
528 also a function-level variable.
530 `spirv.Variable` is modelled as `llvm.alloca` op. If initialized, an additional
531 store instruction is used. Note that there is no initialization for arrays and
532 structs since constants of these types are not supported in LLVM dialect (TODO).
533 Also, at the moment initialization is only possible via `spirv.Constant`.
536 // Conversion of VariableOp without initialization
537 %size = llvm.mlir.constant(1 : i32) : i32
538 %res = spirv.Variable : !spirv.ptr<vector<3xf32>, Function> => %res = llvm.alloca %size x vector<3xf32> : (i32) -> !llvm.ptr
540 // Conversion of VariableOp with initialization
541 %c = llvm.mlir.constant(0 : i64) : i64
542 %c = spirv.Constant 0 : i64 %size = llvm.mlir.constant(1 : i32) : i32
543 %res = spirv.Variable init(%c) : !spirv.ptr<i64, Function> => %res = llvm.alloca %[[SIZE]] x i64 : (i32) -> !llvm.ptr
544 llvm.store %c, %res : i64, !llvm.ptr
547 Note that simple conversion to `alloca` may not be sufficient if the code has
548 some scoping. For example, if converting ops executed in a loop into `alloca`s,
549 a stack overflow may occur. For this case, `stacksave`/`stackrestore` pair can
552 ### Miscellaneous ops with direct conversions
554 There are multiple SPIR-V ops that do not fit in a particular group but can be
555 converted directly to LLVM dialect. Their conversion is addressed in this
558 SPIR-V Dialect op | LLVM Dialect op
559 :---------------: | :---------------:
560 `spirv.Select` | `llvm.select`
561 `spirv.Undef` | `llvm.mlir.undef`
565 Shift operates on two operands: `shift` and `base`.
567 In SPIR-V dialect, `shift` and `base` may have different bit width. On the
568 contrary, in LLVM Dialect both `base` and `shift` have to be of the same
569 bitwidth. This leads to the following conversions:
571 * if `base` has the same bitwidth as `shift`, the conversion is
574 * if `base` has a greater bit width than `shift`, shift is sign or zero
575 extended first. Then the extended value is passed to the shift.
577 * otherwise, the conversion is considered to be illegal.
580 // Shift without extension
581 %res0 = spirv.ShiftRightArithmetic %0, %2 : i32, i32 => %res0 = llvm.ashr %0, %2 : i32
583 // Shift with extension
584 %ext = llvm.sext %1 : i16 to i32
585 %res1 = spirv.ShiftRightArithmetic %0, %1 : i32, i16 => %res1 = llvm.ashr %0, %ext: i32
590 At the moment `spirv.Constant` conversion supports scalar and vector constants
595 `spirv.Constant` is mapped to `llvm.mlir.constant`. This is a straightforward
596 conversion pattern with a special case when the argument is signed or unsigned.
600 SPIR-V constant can be a signed or unsigned integer. Since LLVM Dialect does not
601 have signedness semantics, this case should be handled separately.
603 The conversion casts constant value attribute to a signless integer or a vector
604 of signless integers. This is correct because in SPIR-V, like in LLVM, how to
605 interpret an integer number is also dictated by the opcode. However, in reality
606 hardware implementation might show unexpected behavior. Therefore, it is better
607 to handle it case-by-case, given that the purpose of the conversion is not to
608 cover all possible corner cases.
611 // %0 = llvm.mlir.constant(0 : i8) : i8
612 %0 = spirv.Constant 0 : i8
614 // %1 = llvm.mlir.constant(dense<[2, 3, 4]> : vector<3xi32>) : vector<3xi32>
615 %1 = spirv.Constant dense<[2, 3, 4]> : vector<3xui32>
618 ### Not implemented ops
620 There is no support of the following ops:
629 * spirv.CompositeConstruct
630 * spirv.ControlBarrier
641 * spirv.MemoryBarrier
642 * spirv.mlir.referenceof
646 * spirv.VectorExtractDynamic
648 ## Control flow conversion
652 `spirv.Branch` and `spirv.BranchConditional` are mapped to `llvm.br` and
653 `llvm.cond_br`. Branch weights for `spirv.BranchConditional` are mapped to
654 corresponding `branch_weights` attribute of `llvm.cond_br`. When translated to
655 proper LLVM, `branch_weights` are converted into LLVM metadata associated with
656 the conditional branch.
658 ### `spirv.FunctionCall`
660 `spirv.FunctionCall` maps to `llvm.call`. For example:
663 %0 = spirv.FunctionCall @foo() : () -> i32 => %0 = llvm.call @foo() : () -> f32
664 spirv.FunctionCall @bar(%0) : (i32) -> () => llvm.call @bar(%0) : (f32) -> ()
667 ### `spirv.mlir.selection` and `spirv.mlir.loop`
669 Control flow within `spirv.mlir.selection` and `spirv.mlir.loop` is lowered directly
670 to LLVM via branch ops. The conversion can only be applied to selection or loop
671 with all blocks being reachable. Moreover, selection and loop control attributes
672 (such as `Flatten` or `Unroll`) are not supported at the moment.
675 // Conversion of selection
676 %cond = spirv.Constant true %cond = llvm.mlir.constant(true) : i1
677 spirv.mlir.selection {
678 spirv.BranchConditional %cond, ^true, ^false llvm.cond_br %cond, ^true, ^false
681 // True block code // True block code
682 spirv.Branch ^merge => llvm.br ^merge
685 // False block code // False block code
686 spirv.Branch ^merge llvm.br ^merge
689 spirv.mlir.merge llvm.br ^continue
691 // Remaining code ^continue:
696 // Conversion of loop
697 %cond = spirv.Constant true %cond = llvm.mlir.constant(true) : i1
699 spirv.Branch ^header llvm.br ^header
702 // Header code // Header code
703 spirv.BranchConditional %cond, ^body, ^merge => llvm.cond_br %cond, ^body, ^merge
706 // Body code // Body code
707 spirv.Branch ^continue llvm.br ^continue
709 ^continue: ^continue:
710 // Continue code // Continue code
711 spirv.Branch ^header llvm.br ^header
714 spirv.mlir.merge llvm.br ^remaining
716 // Remaining code ^remaining:
720 ## Decorations conversion
722 **Note: these conversions have not been implemented yet**
724 ## GLSL extended instruction set
726 This section describes how SPIR-V ops from GLSL extended instructions set are
727 mapped to LLVM Dialect.
729 ### Direct conversions
731 SPIR-V Dialect op | LLVM Dialect op
732 :---------------: | :----------------:
733 `spirv.GL.Ceil` | `llvm.intr.ceil`
734 `spirv.GL.Cos` | `llvm.intr.cos`
735 `spirv.GL.Exp` | `llvm.intr.exp`
736 `spirv.GL.FAbs` | `llvm.intr.fabs`
737 `spirv.GL.Floor` | `llvm.intr.floor`
738 `spirv.GL.FMax` | `llvm.intr.maxnum`
739 `spirv.GL.FMin` | `llvm.intr.minnum`
740 `spirv.GL.Log` | `llvm.intr.log`
741 `spirv.GL.Sin` | `llvm.intr.sin`
742 `spirv.GL.Sqrt` | `llvm.intr.sqrt`
743 `spirv.GL.SMax` | `llvm.intr.smax`
744 `spirv.GL.SMin` | `llvm.intr.smin`
748 `spirv.InverseSqrt` is mapped to:
751 %one = llvm.mlir.constant(1.0 : f32) : f32
752 %res = spirv.InverseSqrt %arg : f32 => %sqrt = "llvm.intr.sqrt"(%arg) : (f32) -> f32
753 %res = fdiv %one, %sqrt : f32
756 `spirv.Tan` is mapped to:
759 %sin = "llvm.intr.sin"(%arg) : (f32) -> f32
760 %res = spirv.Tan %arg : f32 => %cos = "llvm.intr.cos"(%arg) : (f32) -> f32
761 %res = fdiv %sin, %cos : f32
764 `spirv.Tanh` is modelled using the equality `tanh(x) = {exp(2x) - 1}/{exp(2x) +
768 %two = llvm.mlir.constant(2.0: f32) : f32
769 %2xArg = llvm.fmul %two, %arg : f32
770 %exp = "llvm.intr.exp"(%2xArg) : (f32) -> f32
771 %res = spirv.Tanh %arg : f32 => %one = llvm.mlir.constant(1.0 : f32) : f32
772 %num = llvm.fsub %exp, %one : f32
773 %den = llvm.fadd %exp, %one : f32
774 %res = llvm.fdiv %num, %den : f32
777 ## Function conversion and related ops
779 This section describes the conversion of function-related operations from SPIR-V
784 This op declares or defines a SPIR-V function and it is converted to
785 `llvm.func`. This conversion handles signature conversion, and function control
786 attributes remapping to LLVM dialect function
787 [`passthrough` attribute](Dialects/LLVM.md/#attribute-pass-through).
789 The following mapping is used to map
790 [SPIR-V function control][SPIRVFunctionAttributes] to
791 [LLVM function attributes][LLVMFunctionAttributes]:
793 SPIR-V Function Control Attributes | LLVM Function Attributes
794 :--------------------------------: | :---------------------------:
795 None | No function attributes passed
796 Inline | `alwaysinline`
797 DontInline | `noinline`
801 ### `spirv.Return` and `spirv.ReturnValue`
803 In LLVM IR, functions may return either 1 or 0 value. Hence, we map both ops to
804 `llvm.return` with or without a return value.
808 Module in SPIR-V has one region that contains one block. It is defined via
809 `spirv.module` op that also takes a range of attributes:
813 * Version-Capability-Extension attribute
815 `spirv.module` is converted into `ModuleOp`. This plays a role of enclosing scope
816 to LLVM ops. At the moment, SPIR-V module attributes are ignored.
818 ## SPIR-V CPU Runner Tests
820 The `mlir-cpu-runner` has support for executing a `gpu` dialect kernel on the
821 CPU via SPIR-V to LLVM dialect conversion. This is referred to as the "SPIR-V
822 CPU Runner". The `--link-nested-modules` flag needs to be passed for this.
823 Currently, only single-threaded kernels are supported.
825 To build the required runtime libaries, add the following option to `cmake`:
826 `-DMLIR_ENABLE_SPIRV_CPU_RUNNER=1`
830 The `gpu` module with the kernel and the host code undergo the following
833 * Convert the `gpu` module into SPIR-V dialect, lower ABI attributes and
834 update version, capability and extension.
836 * Emulate the kernel call by converting the launching operation into a normal
837 function call. The data from the host side to the device is passed via
838 copying to global variables. These are created in both the host and the
839 kernel code and later linked when nested modules are folded.
841 * Convert SPIR-V dialect kernel to LLVM dialect via the new conversion path.
843 After these passes, the IR transforms into a nested LLVM module - a main module
844 representing the host code and a kernel module. These modules are linked and
845 executed using `ExecutionEngine`.
849 This section gives a detailed overview of the IR changes while running
850 SPIR-V CPU Runner tests. First, consider that we have the following IR. (For
851 simplicity some type annotations and function implementations have been
856 gpu.func @bar(%arg: memref<8xi32>) {
863 // Fill the buffer with some data
864 %buffer = memref.alloc : memref<8xi32>
866 call fillBuffer(%buffer, %data)
868 "gpu.launch_func"(/*grid dimensions*/, %buffer) {
874 Lowering `gpu` dialect to SPIR-V dialect results in
877 spirv.module @__spv__foo /*VCE triple and other metadata here*/ {
878 spirv.GlobalVariable @__spv__foo_arg bind(0,0) : ...
882 spirv.EntryPoint @bar, ...
886 // Fill the buffer with some data.
887 %buffer = memref.alloc : memref<8xi32>
889 call fillBuffer(%buffer, %data)
891 "gpu.launch_func"(/*grid dimensions*/, %buffer) {
897 Then, the lowering from standard dialect to LLVM dialect is applied to the host
901 spirv.module @__spv__foo /*VCE triple and other metadata here*/ {
902 spirv.GlobalVariable @__spv__foo_arg bind(0,0) : ...
906 spirv.EntryPoint @bar, ...
909 // Kernel function declaration.
910 llvm.func @__spv__foo_bar() : ...
913 // Fill the buffer with some data.
914 llvm.call fillBuffer(%buffer, %data)
916 // Copy data to the global variable, call kernel, and copy the data back.
917 %addr = llvm.mlir.addressof @__spv__foo_arg_descriptor_set0_binding0 : ...
918 "llvm.intr.memcpy"(%addr, %buffer) : ...
919 llvm.call @__spv__foo_bar()
920 "llvm.intr.memcpy"(%buffer, %addr) : ...
926 Finally, SPIR-V module is converted to LLVM and the symbol names are resolved
931 llvm.mlir.global @__spv__foo_arg_descriptor_set0_binding0 : ...
932 llvm.func @__spv__foo_bar() {
937 // Kernel function declaration.
938 llvm.func @__spv__foo_bar() : ...
941 // Fill the buffer with some data.
942 llvm.call fillBuffer(%buffer, %data)
944 // Copy data to the global variable, call kernel, and copy the data back.
945 %addr = llvm.mlir.addressof @__spv__foo_arg_descriptor_set0_binding0 : ...
946 "llvm.intr.memcpy"(%addr, %buffer) : ...
947 llvm.call @__spv__foo_bar()
948 "llvm.intr.memcpy"(%buffer, %addr) : ...
954 [LLVMFunctionAttributes]: https://llvm.org/docs/LangRef.html#function-attributes
955 [SPIRVFunctionAttributes]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_function_control_a_function_control
956 [VulkanLayoutUtils]: https://github.com/llvm/llvm-project/blob/main/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h