[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
This commit is contained in:
George Mitenkov 2020-08-05 09:27:03 +03:00
parent b989fcbae6
commit 521c0b2659
1 changed files with 216 additions and 29 deletions

View File

@ -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 difference 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 difference 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