DirectXShaderCompiler/docs/SPIR-V.rst

488 строки
27 KiB
ReStructuredText
Исходник Обычный вид История

2017-04-15 04:18:21 +03:00
==============
SPIR-V Codegen
==============
.. contents::
:local:
:depth: 2
Introduction
============
This document describes the designs and logistics for supporting SPIR-V codegen functionality. `SPIR-V <https://www.khronos.org/registry/spir-v/>`_ is a binary intermediate language for representing graphical-shader stages and compute kernels for multiple Khronos APIs, such as Vulkan, OpenGL, and OpenCL. At the moment we only intend to support the Vulkan flavor of SPIR-V.
DirectXShaderCompiler is the reference compiler for HLSL. Adding SPIR-V codegen in DirectXShaderCompiler will enable the usage of HLSL as a frontend language for Vulkan shader programming. Sharing the same code base also means we can track the evolution of HLSL more closely and always deliver the best of HLSL to developers. Moreover, developers will also have a unified compiler toolchain for targeting both DirectX and Vulkan. We believe this effort will benefit the general graphics ecosystem.
Designs
=======
Various designs are driven by technical considerations together with the following guidelines for good citizenship within DirectXShaderCompiler:
- Conduct minimal changes to existing interfaces and libraries
- Perfer less intrusive solutions
General approach
----------------
The general approach is to translate frontend AST directly into SPIR-V binary. We choose this approach considering that
- Frontend AST is much more higher-level than DXIL. For example, `DXIL scalarized vectors <https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/DXIL.rst#vectors>`_ but SPIR-V has native support.
- DXIL has widely different semantics than Vulkan flavor of SPIR-V. For example, `structured control flow is not preserved in DXIL <https://github.com/Microsoft/DirectXShaderCompiler/blob/master/docs/DXIL.rst#control-flow-restrictions>`_ but SPIR-V for Vulkan requires it.
- Frontend AST perserves the information in the source code better.
- Also, the right place to generate error messages is in Clang's semantic analysis step, which is when the compiler is still processing the AST.
Therefore, it is easier to go from frontend AST to SPIR-V than from DXIL since we do not need to rediscover certain information.
LLVM optimization passes
++++++++++++++++++++++++
Translating frontend AST directly into SPIR-V binary precludes the usage of existing LLVM optimization passes. This is expected since there are also subtle semantics differences between SPIR-V and LLVM IR. Certain concepts in SPIR-V do not have direct corresponding representation in LLVM IR and there are no existing translation schemes handling the differences. Using vanilla LLVM optimization passes will likely violate the requirements of SPIR-V and results in invalid SPIR-V modules.
Library
-------
On the library side, this means introducing a new ``ASTFrontendAction`` and a SPIR-V module builder. The new frontend action will traverse the AST and call the SPIR-V module builder to construct SPIR-V words. These code should be placed at ``tools/clang/lib/SPIRV`` and packed into one library (or multiple libraries in the future).
Detailed design will be revised to accommodate more and more HLSL features. At the moment, we have::
EmitSPIRVAction
|
| creates
V
SPIRVEmitter
|
| contains
|
+-------------+------------+
| |
| |
V references V
SPIRVContext <------------ ModuleBuilder
|
| contains
V
InstBuilder
|
| depends on
V
WordConsumer
``SPIRVEmitter``
The derived ``ASTConsumer`` which acts on various frontend AST nodes by calling corresponding ``ModuleBuilder`` methods to build SPIR-V modules gradually.
``ModuleBuilder``
Exposes API for constructing SPIR-V modules. Internally it has structured representation of SPIR-V modules, functions, basic blocks as well as various SPIR-V specific structs like entry points, debug names, and so on.
``SPIRVContext``
Responsible for <result-id> allocation and maintaining the lifetime of objects allocated to represent types, decorations, and others. It is used in conjunction with ``ModuleBuilder``.
``InstBuilder``
The low-level interface for generating SPIR-V words for various SPIR-V instructions. All SPIR-V instructions are eventually serialized via ``InstBuilder``.
``WordConsumer``
The consumer of generated SPIR-V words.
Command-line tool
-----------------
On the command-line tool side, this means introducing a new binary, ``hlsl2spirv`` to wrap around the library functionality.
But as the initial scaffolding step, a new option, ``-spirv``, will be added into ``dxc`` for invoking the new SPIR-V codegen action.
Build system
------------
SPIR-V codegen functionality will require two external projects: `SPIRV-Headers <https://github.com/KhronosGroup/SPIRV-Headers>`_ (for ``spirv.hpp11``) and `SPIRV-Tools <https://github.com/KhronosGroup/SPIRV-Tools>`_ (for SPIR-V disassembling). These two projects should be checked out under the ``external/`` directory.
SPIR-V codegen functionality will structured as an optional feature in DirectXShaderCompiler. Two new CMake options will be introduced to control the configuring and building SPIR-V codegen:
- ``ENABLE_SPIRV_CODEGEN``: If turned on, enables the SPIR-V codegen functionality. (Default: OFF)
- ``SPIRV_BUILD_TESTS``: If turned on, enables building of SPIR-V related tests. This option will also implicitly turn on ``ENABLE_SPIRV_CODEGEN``. (Default: OFF)
For building, ``hctbuild`` will be extended with two new switches, ``-spirv`` and ``-spirvtest``, to turn on the above two options, respectively.
For testing, ``hcttest spirv`` will run all existing tests together with SPIR-V tests, while ``htctest spirv_only`` will only trigger SPIR-V tests.
Mapping From HLSL to SPIR-V for Vulkan
======================================
Due to the differences of semantics between DirectX and Vulkan, certain HLSL features do not have corresponding mappings in Vulkan, and certain Vulkan specific information does not have native ways to express in HLSL source code. This section will capture the mappings we use to conduct the translation. Specifically, it lists the mappings from HLSL shader model 6.0 to Vulkan flavor of SPIR-V.
Note that this section is expected to be an ongoing effort and grow as we implement more and more HLSL features. We are likely to extract the contents in this section into a new doc in the future.
Vulkan semantics
----------------
To provide additional information required by Vulkan in HLSL, we need to extend the syntax of HLSL. `C++ attribute specifier sequence <http://en.cppreference.com/w/cpp/language/attributes>`_ is a non-intrusive way of achieving such purpose.
An example is specifying the layout of Vulkan resources::
[[using Vulkan: set(X), binding(Y)]]
tbuffer TbufOne {
[[using Vulkan: offset(Z)]]
float4 field;
};
[[using Vulkan: push_constant]]
tbuffer TbufTwo {
float4 field;
};
[[using Vulkan: constant_id(M)]]
const int specConst = N;
Types
-----
Normal scalar types
+++++++++++++++++++
`Normal scalar types <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509646(v=vs.85).aspx>`_ in HLSL are relatively easy to handle and can be mapped directly to SPIR-V instructions:
================== ==================
HLSL SPIR-V
================== ==================
``bool`` ``OpTypeBool``
``int`` ``OpTypeInt 32 1``
``uint``/``dword`` ``OpTypeInt 32 0``
``half`` ``OpTypeFloat 16``
``float`` ``OpTypeFloat 32``
``double`` ``OpTypeFloat 64``
================== ==================
Minimal precision scalar types
++++++++++++++++++++++++++++++
HLSL also supports various `minimal precision scalar types <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509646(v=vs.85).aspx>`_, which graphics drivers can implement by using any precision greater than or equal to their specified bit precision.
2017-04-15 04:18:21 +03:00
- ``min16float`` - minimum 16-bit floating point value
- ``min10float`` - minimum 10-bit floating point value
- ``min16int`` - minimum 16-bit signed integer
- ``min12int`` - minimum 12-bit signed integer
- ``min16uint`` - minimum 16-bit unsigned integer
There are no direct mapping in SPIR-V for these types. We may need to use ``OpTypeFloat``/``OpTypeInt`` with ``RelaxedPrecision`` for some of them and issue warnings/errors for the rest.
Vectors and matrices
2017-04-15 04:18:21 +03:00
++++++++++++++++++++
`Vectors <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509707(v=vs.85).aspx>`_ and `matrices <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509623(v=vs.85).aspx>`_ are translated into:
2017-04-15 04:18:21 +03:00
+-------------------------------------+---------------------------------------+
| HLSL | SPIR-V |
+-------------------------------------+---------------------------------------+
| ``|type||count|`` | |
+-------------------------------------+ ``OpTypeVector |type| |count|`` |
| ``vector<|type|, |count|>`` | |
+-------------------------------------+---------------------------------------+
| ``matrix<|type|, |row|, |column|>`` | ``%v = OpTypeVector |type| |column|`` |
+-------------------------------------+ |
| ``|type||row|x|column|`` | ``OpTypeMatrix %v |row|`` |
+-------------------------------------+---------------------------------------+
A MxN HLSL matrix is translated into a SPIR-V matrix with M columns, each with N elements. Conceptually HLSL matrices are row-major while SPIR-V matrices are column-major, thus all HLSL matrices are represented by their transposes. Doing so may require special handling of certain matrix operations:
- **Indexing**: no special handling required. ``matrix[m][n]`` will still access the correct element since ``m``/``n`` means the ``m``-th/``n``-th row/column in HLSL but ``m``-th/``n``-th column/element in SPIR-V.
- **Per-element operation**: no special handling required.
- **Matrix multiplication**: need to swap the operands. ``mat1 x mat2`` should be translated as ``transpose(mat2) x transpose(mat1)``. Then the result is ``transpose(mat1 x mat2)``.
- **Storage layout**: ``row_major``/``column_major`` will be translated into SPIR-V ``ColMajor``/``RowMajor`` decoration. This is because HLSL matrix row/column becomes SPIR-V matrix column/row. If elements in a row/column are packed together, they should be loaded into a column/row correspondingly.
2017-04-15 04:18:21 +03:00
Note that vectors of size 1 are just translated into scalar values of the element types since SPIR-V mandates the size of vector to be at least 2.
Also, matrices whose row or column count is 1 are also translated into the corresponding vector types with the same element type. Matrices of size 1x1 are translated into scalars.
2017-04-15 04:18:21 +03:00
Structs
+++++++
`Structs <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509668(v=vs.85).aspx>`_ in HLSL are defined in the a format similar to C structs, with optional interpolation modifiers for members:
=========================== =================
HLSL Interpolation Modifier SPIR-V Decoration
=========================== =================
``linear`` <none>
``centroid`` ``Centroid``
``nointerpolation`` ``Flat``
``noperspective`` ``NoPerspective``
``sample`` ``Sample``
=========================== =================
User-defined types
++++++++++++++++++
`User-defined types <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509702(v=vs.85).aspx>`_ are type aliases introduced by typedef. No new types are introduced and we can rely on Clang to resolve to the original types.
Samplers and textures
+++++++++++++++++++++
[TODO]
Buffers
+++++++
[TODO]
Variables and resources
-----------------------
Definition
++++++++++
Variables are defined in HLSL using the following `syntax <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509706(v=vs.85).aspx>`_ rules::
[StorageClass] [TypeModifier] Type Name[Index]
[: Semantic]
[: Packoffset]
[: Register];
[Annotations]
[= InitialValue]
Storage class
+++++++++++++
[TODO]
Type modifier
+++++++++++++
[TODO]
Interface variables
+++++++++++++++++++
Direct3D uses "`semantics <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509647(v=vs.85).aspx>`_" to compose and match the interfaces between subsequent stages. These semantics modifiers can appear after struct members, global variables, and also function parameters and return values. E.g.,::
struct VSInput {
float4 pos : POSITION;
float3 norm : NORMAL;
float4 tex : TEXCOORD0;
};
float4 pos: SV_POSITION;
float4 VSFunction(float4 pos : POSITION) : POSITION {
return pos;
}
In Clang AST, these semantics are represented as ``SemanticDecl``, which is attached to the corresponding struct members (``FieldDecl``), global variables (``VarDecl``), and function parameters (``ParmVarDecl``) and return values (``FunctionDecl``).
[TODO] How to map semantics to SPIR-V interface variables
Expressions
-----------
Arithmetic operators
++++++++++++++++++++
`Arithmetic operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Additive_and_Multiplicative_Operators>`_ (``+``, ``-``, ``*``, ``/``, ``%``) are translated into their corresponding SPIR-V opcodes according to the following table.
+-------+-----------------------------+-------------------------------+--------------------+
| | (Vector of) Signed Integers | (Vector of) Unsigned Integers | (Vector of) Floats |
+-------+-----------------------------+-------------------------------+--------------------+
| ``+`` | ``OpIAdd`` | ``OpFAdd`` |
+-------+-------------------------------------------------------------+--------------------+
| ``-`` | ``OpISub`` | ``OpFSub`` |
+-------+-------------------------------------------------------------+--------------------+
| ``*`` | ``OpIMul`` | ``OpFMul`` |
+-------+-----------------------------+-------------------------------+--------------------+
| ``/`` | ``OpSDiv`` | ``OpUDiv`` | ``OpFDiv`` |
+-------+-----------------------------+-------------------------------+--------------------+
| ``%`` | ``OpSRem`` | ``OpUMod`` | ``OpFRem`` |
+-------+-----------------------------+-------------------------------+--------------------+
Note that for modulo operation, SPIR-V has two sets of instructions: ``Op*Rem`` and ``Op*Mod``. For ``Op*Rem``, the sign of a non-0 result comes from the first operand; while for ``Op*Mod``, the sign of a non-0 result comes from the second operand. HLSL doc does not mandate which set of instructions modulo operations should be translated into; it only says "the % operator is defined only in cases where either both sides are positive or both sides are negative." So technically it's undefined behavior to use the modulo operation with operands of different signs. But considering HLSL's C heritage and the behavior of Clang frontend, we translate modulo operators into ``Op*Rem`` (there is no ``OpURem``).
For multiplications of float vectors and float scalars, the dedicated SPIR-V operation ``OpVectorTimesScalar`` will be used.
Bitwise operators
+++++++++++++++++
`Bitwise operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Bitwise_Operators>`_ (``~``, ``&``, ``|``, ``^``, ``<<``, ``>>``) are translated into their corresponding SPIR-V opcodes according to the following table.
+--------+-----------------------------+-------------------------------+
| | (Vector of) Signed Integers | (Vector of) Unsigned Integers |
+--------+-----------------------------+-------------------------------+
| ``~`` | ``OpNot`` |
+--------+-------------------------------------------------------------+
| ``&`` | ``OpBitwiseAnd`` |
+--------+-------------------------------------------------------------+
| ``|`` | ``OpBitwiseOr`` |
+--------+-----------------------------+-------------------------------+
| ``^`` | ``OpBitwiseXor`` |
+--------+-----------------------------+-------------------------------+
| ``<<`` | ``OpShiftLeftLogical`` |
+--------+-----------------------------+-------------------------------+
| ``>>`` | ``OpShiftRightArithmetic`` | ``OpShiftRightLogical`` |
+--------+-----------------------------+-------------------------------+
Comparison operators
++++++++++++++++++++
`Comparison operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Comparison_Operators>`_ (``<``, ``<=``, ``>``, ``>=``, ``==``, ``!=``) are translated into their corresponding SPIR-V opcodes according to the following table.
+--------+-----------------------------+-------------------------------+------------------------------+
| | (Vector of) Signed Integers | (Vector of) Unsigned Integers | (Vector of) Floats |
+--------+-----------------------------+-------------------------------+------------------------------+
| ``<`` | ``OpSLessThan`` | ``OpULessThan`` | ``OpFOrdLessThan`` |
+--------+-----------------------------+-------------------------------+------------------------------+
| ``<=`` | ``OpSLessThanEqual`` | ``OpULessThanEqual`` | ``OpFOrdLessThanEqual`` |
+--------+-----------------------------+-------------------------------+------------------------------+
| ``>`` | ``OpSGreaterThan`` | ``OpUGreaterThan`` | ``OpFOrdGreaterThan`` |
+--------+-----------------------------+-------------------------------+------------------------------+
| ``>=`` | ``OpSGreaterThanEqual`` | ``OpUGreaterThanEqual`` | ``OpFOrdGreaterThanEqual`` |
+--------+-----------------------------+-------------------------------+------------------------------+
| ``==`` | ``OpIEqual`` | ``OpFOrdEqual`` |
+--------+-------------------------------------------------------------+------------------------------+
| ``!=`` | ``OpINotEqual`` | ``OpFOrdNotEqual`` |
+--------+-------------------------------------------------------------+------------------------------+
Note that for comparison of (vectors of) floats, SPIR-V has two sets of instructions: ``OpFOrd*``, ``OpFUnord*``. We translate into ``OpFOrd*`` ones.
2017-04-15 04:18:21 +03:00
Boolean math operators
++++++++++++++++++++++
`Boolean match operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Boolean_Math_Operators>`_ (``&&``, ``||``, ``?:``) are translated into their corresponding SPIR-V opcodes according to the following table.
+--------+----------------------+
| | (Vector of) Booleans |
+--------+----------------------+
| ``&&`` | ``OpLogicalAnd`` |
+--------+----------------------+
| ``||`` | ``OpLogicalOr`` |
+--------+----------------------+
| ``?:`` | ``OpSelect`` |
+--------+----------------------+
Please note that "unlike short-circuit evaluation of ``&&``, ``||``, and ``?:`` in C, HLSL expressions never short-circuit an evaluation because they are vector operations. All sides of the expression are always evaluated."
Unary operators
+++++++++++++++
For `unary operators <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509631(v=vs.85).aspx#Unary_Operators>`_:
- ``!`` is translated into ``OpLogicalNot``. Parsing will gurantee the operands are of boolean types by inserting necessary casts.
- ``+`` requires no additional SPIR-V instructions.
- ``-`` is translated into ``OpSNegate`` and ``OpFNegate`` for (vectors of) integers and floats, respectively.
Casts
+++++
Casting between (vectors) of scalar types is translated according to the following table:
+------------+-------------------+-------------------+-------------------+-------------------+
| From \\ To | Bool | SInt | UInt | Float |
+------------+-------------------+-------------------+-------------------+-------------------+
| Bool | no-op | select between one and zero |
+------------+-------------------+-------------------+-------------------+-------------------+
| SInt | | no-op | ``OpBitcast`` | ``OpConvertSToF`` |
+------------+ +-------------------+-------------------+-------------------+
| UInt | compare with zero | ``OpBitcast`` | no-op | ``OpConvertUToF`` |
+------------+ +-------------------+-------------------+-------------------+
| Float | | ``OpConvertFToS`` | ``OpConvertFToU`` | no-op |
+------------+-------------------+-------------------+-------------------+-------------------+
Indexing operator
+++++++++++++++++
The ``[]`` operator can also be used to access elements in a matrix or vector. A matrix whose row and/or column count is 1 will be translated into a vector or scalar. If a variable is used as the index for the dimension whose count is 1, that variable will be ignored in the generated SPIR-V code. This is because out-of-bound indexing triggers undefined behavior anyway. For example, for a 1xN matrix ``mat``, ``mat[index][0]`` will be translated into ``OpAccessChain ... %mat %uint_0``. Similarly, variable index into a size 1 vector will also be ignored and the only element will be always returned.
2017-04-15 04:18:21 +03:00
Control flows
-------------
Switch Statements
+++++++++++++++++
HLSL `switch statements <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509669(v=vs.85).aspx>`_ are translated into SPIR-V using:
- **OpSwitch**: if (all case values are integer literals or constant integer variables) and (no attribute or the ``forcecase`` attribute is specified)
- **A series of if statements**: for all other scenarios (e.g., when ``flatten``, ``branch``, or ``call`` attribute is specified)
Loops
+++++
HLSL `for statements <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509602(v=vs.85).aspx>`_ and `while statements <https://msdn.microsoft.com/en-us/library/windows/desktop/bb509708(v=vs.85).aspx>`_ are translated into SPIR-V by constructing all necessary basic blocks and using ``OpLoopMerge`` to organize as structured loops.
The HLSL attributes for these statements are translated into SPIR-V loop control masks according to the following table:
+-------------------------+--------------------------------------------------+
| HLSL loop attribute | SPIR-V Loop Control Mask |
+-------------------------+--------------------------------------------------+
| ``unroll(x)`` | ``Unroll`` |
+-------------------------+--------------------------------------------------+
| ``loop`` | ``DontUnroll`` |
+-------------------------+--------------------------------------------------+
| ``fastopt`` | ``DontUnroll`` |
+-------------------------+--------------------------------------------------+
| ``allow_uav_condition`` | Currently Unimplemented |
+-------------------------+--------------------------------------------------+
2017-04-15 04:18:21 +03:00
Functions
---------
All functions reachable from the entry-point function will be translated into SPIR-V code. Functions not reachable from the entry-point function will be ignored.
Function parameter
++++++++++++++++++
For a function ``f`` which has a parameter of type ``T``, the generated SPIR-V signature will use type ``T*`` for the parameter. At every call site of ``f``, additional local variables will be allocated to hold the actual arguments. The local variables are passed in as direct function arguments. For example::
// HLSL source code
float4 f(float a, int b) { ... }
void caller(...) {
...
float4 result = f(...);
...
}
// SPIR-V code
...
%i32PtrType = OpTypePointer Function %int
%f32PtrType = OpTypePointer Function %float
%fnType = OpTypeFunction %v4float %f32PtrType %i32PtrType
...
%f = OpFunction %v4float None %fnType
%a = OpFunctionParameter %f32PtrType
%b = OpFunctionParameter %i32PtrType
...
%caller = OpFunction ...
...
%aAlloca = OpVariable %_ptr_Function_float Function
%bAlloca = OpVariable %_ptr_Function_int Function
...
OpStore %aAlloca ...
OpStore %bAlloca ...
%result = OpFunctioncall %v4float %f %aAlloca %bAlloca
...
This approach gives us unified handling of function parameters and local variables: both of them are accessed via load/store instructions.
Intrinsic functions
2017-04-15 04:18:21 +03:00
-----------------
The following intrinsic HLSL functions are currently supported:
- `dot` : performs dot product of two vectors, each containing floats or integers. If the two parameters are vectors of floats, we use SPIR-V's OpDot instruction to perform the translation. If the two parameters are vectors of integers, we multiply corresponding vector elementes using OpIMul and accumulate the results using OpIAdd to compute the dot product.
2017-04-15 04:18:21 +03:00
Logistics
=========
Project planning
----------------
We use `GitHub Project feature in the Google fork repo <https://github.com/google/DirectXShaderCompiler/projects/1>`_ to manage tasks and track progress.
Pull requests and code review
-----------------------------
Pull requests are very welcome! However, the Google repo is only used for project planning. We do not intend to maintain a detached fork; so all pull requests should be sent against the original `Microsoft repo <https://github.com/Microsoft/DirectXShaderCompiler>`_. Code reviews will also happen there.
For each pull request, please make sure
- You express your intent in the Google fork to avoid duplicate work.
- Tests are written to cover the modifications.
- This doc is updated for newly supported features.
Testing
-------
We will use `googletest <https://github.com/google/googletest>`_ as the unit test and codegen test framework. Appveyor will be used to check regression of all pull requests.