[Mlir-commits] [mlir] 521c0b2 - [MLIR][SPIRVToLLVM] Updated documentation for SPIR-V to LLVM conversion
George Mitenkov
llvmlistbot at llvm.org
Tue Aug 4 23:40:13 PDT 2020
Author: George Mitenkov
Date: 2020-08-05T09:38:45+03:00
New Revision: 521c0b2659074c512d292dc30da78c862782d34c
URL: https://github.com/llvm/llvm-project/commit/521c0b2659074c512d292dc30da78c862782d34c
DIFF: https://github.com/llvm/llvm-project/commit/521c0b2659074c512d292dc30da78c862782d34c.diff
LOG: [MLIR][SPIRVToLLVM] Updated documentation for SPIR-V to LLVM conversion
Updated the documentation for SPIR-V to LLVM conversion, particularly:
- Added a section on control flow
- Added a section on memory ops
- Added a section on GLSL ops
Also, moved `spv.FunctionCall` to control flow section. Added a new section
that will be used to describe the modelling of runtime-related ops.
Reviewed By: antiagainst
Differential Revision: https://reviews.llvm.org/D84734
Added:
Modified:
mlir/docs/SPIRVToLLVMDialectConversion.md
Removed:
################################################################################
diff --git a/mlir/docs/SPIRVToLLVMDialectConversion.md b/mlir/docs/SPIRVToLLVMDialectConversion.md
index 1f76741bb2ed..dcc872f59542 100644
--- a/mlir/docs/SPIRVToLLVMDialectConversion.md
+++ b/mlir/docs/SPIRVToLLVMDialectConversion.md
@@ -88,8 +88,8 @@ at the moment. Hence, we adhere to the following mapping:
Examples of SPIR-V struct conversion are:
```mlir
-!spv.struct<i8, i32> => !llvm<"<{ i8, i32> }>">
-!spv.struct<i8 [0], i32 [4]> => !llvm<"{ i8, i32> }">
+!spv.struct<i8, i32> => !llvm<"<{ i8, i32 }>">
+!spv.struct<i8 [0], i32 [4]> => !llvm<"{ i8, i32 }">
// error
!spv.struct<i8 [0], i32 [8]>
@@ -373,6 +373,116 @@ modelled with `xor` operation with a mask with all bits set.
%0 = spv.LogicalNot %op : i1 => %0 = llvm.xor %op, %mask : !llvm.i1
```
+### Memory ops
+
+This section describes the conversion patterns for SPIR-V dialect operations
+that concern memory.
+
+#### `spv.Load` and `spv.Store`
+
+These ops are converted to their LLVM counterparts: `llvm.load` and
+`llvm.store`. If the op has a memory access attribute, then there are the
+following cases, based on the value of the attribute:
+
+* **Aligned**: alignment is passed on to LLVM op builder, for example:
+ ```mlir
+ // llvm.store %ptr, %val {alignment = 4 : i64} : !llvm<"float*">
+ spv.Store "Function" %ptr, %val ["Aligned", 4] : f32
+ ```
+* **None**: same case as if there is no memory access attribute.
+
+* **Nontemporal**: set `nontemporal` flag, for example:
+ ```mlir
+ // %res = llvm.load %ptr {nontemporal} : !llvm<"float*">
+ %res = spv.Load "Function" %ptr ["Nontemporal"] : f32
+ ```
+* **Volatile**: mark the op as `volatile`, for example:
+ ```mlir
+ // %res = llvm.load volatile %ptr : !llvm<"float*">
+ %res = spv.Load "Function" %ptr ["Volatile"] : f32
+ ```
+Otherwise the conversion fails as other cases (`MakePointerAvailable`,
+`MakePointerVisible`, `NonPrivatePointer`) are not supported yet.
+
+#### `spv.globalVariable` and `spv._address_of`
+
+`spv.globalVariable` is modelled with `llvm.mlir.global` op. However, there
+is a
diff erence that has to be pointed out.
+
+In SPIR-V dialect, the global variable returns a pointer, whereas in LLVM
+dialect the global holds an actual value. This
diff erence is handled by
+`spv._address_of` and `llvm.mlir.addressof` ops that both return a pointer and
+are used to reference the global.
+
+```mlir
+// Original SPIR-V module
+spv.module Logical GLSL450 {
+ spv.globalVariable @struct : !spv.ptr<!spv.struct<f32, !spv.array<10xf32>>, Private>
+ spv.func @func() -> () "None" {
+ %0 = spv._address_of @struct : !spv.ptr<!spv.struct<f32, !spv.array<10xf32>>, Private>
+ spv.Return
+ }
+}
+
+// Converted result
+module {
+ llvm.mlir.global private @struct() : !llvm<"<{ float, [10 x float] }>">
+ llvm.func @func() {
+ %0 = llvm.mlir.addressof @struct : !llvm<"<{ float, [10 x float] }>*">
+ llvm.return
+ }
+}
+```
+
+At the moment, only current invocation is in conversion's scope. This means that
+global variables with pointers of `Input`, `Output` and `Private` storage
+classes are supported. Moreover, `bind` that specifies the descriptor set and
+binding number and `built_in` that specifies SPIR-V `BuiltIn` decoration have
+also no conversion.
+
+Currently `llvm.mlir.global`s are created with `private` linkage for
+`Private` storage class and `External` for `Input`/`Output` storage classes,
+based on SPIR-V spec:
+
+> By default, functions and global variables are private to a module and cannot
+be accessed by other modules. However, a module may be written to export or
+import functions and global (module scope) variables.
+
+If the global variable's pointer has `Input` storage class, then a `constant`
+flag is added to LLVM op:
+
+```mlir
+spv.globalVariable @var : !spv.ptr<f32, Input> => llvm.mlir.global external constant @var() : !llvm.float
+```
+
+#### `spv.Variable`
+
+Per SPIR-V dialect spec, `spv.Variable` allocates an object in memory, resulting
+in a pointer to it, which can be used with `spv.Load` and `spv.Store`. It is
+also a function-level variable.
+
+`spv.Variable` is modelled as `llvm.alloca` op. If initialized, an additional
+store instruction is used. Note that there is no initialization for arrays and
+structs since constants of these types are not supported in LLVM dialect (TODO).
+Also, at the moment initialization is only possible via `spv.constant`.
+
+```mlir
+// Conversion of VariableOp without initialization
+ %size = llvm.mlir.constant(1 : i32) : !llvm.i32
+%res = spv.Variable : !spv.ptr<vector<3xf32>, Function> => %res = llvm.alloca %size x !llvm<"<3 x float>"> : (!llvm.i32) -> !llvm<"<3 x float>*">
+
+// Conversion of VariableOp with initialization
+ %c = llvm.mlir.constant(0 : i64) : !llvm.i64
+%c = spv.constant 0 : i64 %size = llvm.mlir.constant(1 : i32) : !llvm.i32
+%res = spv.Variable init(%c) : !spv.ptr<i64, Function> => %res = llvm.alloca %[[SIZE]] x !llvm.i64 : (!llvm.i32) -> !llvm<"i64*">
+ llvm.store %c, %res : !llvm<"i64*">
+```
+
+Note that simple conversion to `alloca` may not be sufficent if the code has
+some scoping. For example, if converting ops executed in a loop into `alloca`s,
+a stack overflow may occur. For this case, `stacksave`/`stackrestore` pair can
+be used (TODO).
+
### Miscellaneous ops with direct conversions
There are multiple SPIR-V ops that do not fit in a particular group but can be
@@ -445,12 +555,11 @@ There is no support of the following ops:
* All Atomic ops
* All matrix ops
-* All GLSL ops
* All GroupNonUniform ops
+
+As well as:
+
* spv.AccessChain
-* spv._address_of
-* spv.Branch
-* spv.BranchConditional
* spv.CompositeConstruct
* spv.CompositeExtract
* spv.CompositeInsert
@@ -459,23 +568,87 @@ There is no support of the following ops:
* spv.EntryPoint
* spv.ExecutionMode
* spv.FMod
-* spv.globalVariable
-* spv.Load
-* spv.loop
+* spv.GLSL.SAbs
+* spv.GLSL.SSign
+* spv.GLSL.FSign
* spv.MemoryBarrier
-* spv._merge
* spv._reference_of
-* spv.selection
* spv.SMod
* spv.specConstant
-* spv.Store
* spv.SubgroupBallotKHR
-* spv.Variable
* spv.Unreachable
## Control flow conversion
-**Note: these conversions have not been implemented yet**
+### Branch ops
+
+`spv.Branch` and `spv.BranchConditional` are mapped to `llvm.br` and
+`llvm.cond_br`. Branch weigths for `spv.BranchConditional` are mapped to
+coresponding `branch_weights` attribute of `llvm.cond_br`. When translated to
+proper LLVM, `branch_weights` are converted into LLVM metadata associated with
+the conditional branch.
+
+### `spv.FunctionCall`
+
+`spv.FunctionCall` maps to `llvm.call`. For example:
+
+```mlir
+%0 = spv.FunctionCall @foo() : () -> i32 => %0 = llvm.call @foo() : () -> !llvm.float
+spv.FunctionCall @bar(%0) : (i32) -> () => llvm.call @bar(%0) : (!llvm.float) -> ()
+```
+
+### `spv.selection` and `spv.loop`
+
+Control flow within `spv.selection` and `spv.loop` is lowered directly to LLVM
+via branch ops. The conversion can only be applied to selection or loop with all
+blocks being reachable. Moreover, selection and loop control attributes (such as
+`Flatten` or `Unroll`) are not supported at the moment.
+
+```mlir
+// Conversion of selection
+%cond = spv.constant true %cond = llvm.mlir.constant(true) : !llvm.i1
+spv.selection {
+ spv.BranchConditional %cond, ^true, ^false llvm.cond_br %cond, ^true, ^false
+
+^true: ^true:
+ // True block code // True block code
+ spv.Branch ^merge => llvm.br ^merge
+
+^false: ^false:
+ // False block code // False block code
+ spv.Branch ^merge llvm.br ^merge
+
+^merge: ^merge:
+ spv._merge llvm.br ^continue
+}
+// Remaining code ^continue:
+ // Remaining code
+```
+
+```mlir
+// Conversion of loop
+%cond = spv.constant true %cond = llvm.mlir.constant(true) : !llvm.i1
+spv.loop {
+ spv.Branch ^header llvm.br ^header
+
+^header: ^header:
+ // Header code // Header code
+ spv.BranchConditional %cond, ^body, ^merge => llvm.cond_br %cond, ^body, ^merge
+
+^body: ^body:
+ // Body code // Body code
+ spv.Branch ^continue llvm.br ^continue
+
+^continue: ^continue:
+ // Continue code // Continue code
+ spv.Branch ^header llvm.br ^header
+
+^merge: ^merge:
+ spv._merge llvm.br ^remaining
+}
+// Remaining code ^remaining:
+ // Remaining code
+```
## Decorations conversion
@@ -483,8 +656,6 @@ There is no support of the following ops:
## GLSL extended instruction set
-**Note: these conversions have not been implemented yet**
-
This section describes how SPIR-V ops from GLSL extended instructions set are
mapped to LLVM Dialect.
@@ -502,16 +673,34 @@ SPIR-V Dialect op | LLVM Dialect op
`spv.GLSL.Log` | `llvm.intr.log`
`spv.GLSL.Sin` | `llvm.intr.sin`
`spv.GLSL.Sqrt` | `llvm.intr.sqrt`
+`spv.GLSL.SMax` | `llvm.intr.smax`
+`spv.GLSL.SMin` | `llvm.intr.smin`
### Special cases
-TODO: add more patterns for special cases.
+`spv.InverseSqrt` is mapped to:
+```mlir
+ %one = llvm.mlir.constant(1.0 : f32) : !llvm.float
+%res = spv.InverseSqrt %arg : f32 => %sqrt = "llvm.intr.sqrt"(%arg) : (!llvm.float) -> !llvm.float
+ %res = fdiv %one, %sqrt : !llvm.float
+```
`spv.Tan` is mapped to:
```mlir
- %sin = "llvm.intr.sin"(%arg) : (!llvm.float) -> !llvm.float
- %cos = "llvm.intr.cos"(%arg) : (!llvm.float) -> !llvm.float
-%res = spv.Tan %arg : f32 => %res = fdiv %sin, %cos : !llvm.float
+ %sin = "llvm.intr.sin"(%arg) : (!llvm.float) -> !llvm.float
+%res = spv.Tan %arg : f32 => %cos = "llvm.intr.cos"(%arg) : (!llvm.float) -> !llvm.float
+ %res = fdiv %sin, %cos : !llvm.float
+```
+
+`spv.Tanh` is modelled using the equality `tanh(x) = {exp(2x) - 1}/{exp(2x) + 1}`:
+```mlir
+ %two = llvm.mlir.constant(2.0: f32) : !llvm.float
+ %2xArg = llvm.fmul %two, %arg : !llvm.float
+ %exp = "llvm.intr.exp"(%2xArg) : (!llvm.float) -> !llvm.float
+%res = spv.Tanh %arg : f32 => %one = llvm.mlir.constant(1.0 : f32) : !llvm.float
+ %num = llvm.fsub %exp, %one : !llvm.float
+ %den = llvm.fadd %exp, %one : !llvm.float
+ %res = llvm.fdiv %num, %den : !llvm.float
```
## Function conversion and related ops
@@ -535,15 +724,6 @@ DontInline | `noinline`
Pure | `readonly`
Const | `readnone`
-### `spv.FunctionCall`
-
-`spv.FunctionCall` maps to `llvm.call`. For example:
-
-```mlir
-%0 = spv.FunctionCall @foo() : () -> i32 => %0 = llvm.call @foo() : () -> !llvm.float
-spv.FunctionCall @bar(%0) : (i32) -> () => llvm.call @bar(%0) : (!llvm.float) -> ()
-```
-
### `spv.Return` and `spv.ReturnValue`
In LLVM IR, functions may return either 1 or 0 value. Hence, we map both ops to
@@ -563,5 +743,12 @@ to LLVM ops. At the moment, SPIR-V module attributes are ignored.
`spv._module_end` is mapped to an equivalent terminator `ModuleTerminatorOp`.
+## SPIR-V special ops
+
+**Note: this section is due to be implemented in August**
+
+This section describes how SPIR-V specific ops, *e.g* `spv.specConstant`, are
+modelled in LLVM. It also provides information on `mlir-spirv-runner`.
+
[LLVMFunctionAttributes]: https://llvm.org/docs/LangRef.html#function-attributes
[SPIRVFunctionAttributes]: https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html#_a_id_function_control_a_function_control
More information about the Mlir-commits
mailing list