SPIR-V is a low level IR for representing GPU programs (shaders and kernels). It represents a Control Flow Graph in SSA format with all necessary information for a compiler backend to be able to compile into GPU programs.
A SPIR-V module consists of a stream of 32 bit integers, from here on called slots. A module has two parts:
- A header
- A list of instructions
The header occupies the first 5 slots of a SPIR-V binary module. The data contained in the header is:
- Magic number : Identifies a SPIR-V module and helps determining endianness. (0x07230203 for big endian and 0x03022307 for little endian)
- SPIR-V Version: The version of the SPIR-V specification this module adheres to.
- Generator ID: Magic number for the tool that generated this SPIR-V module. Is allowed to be 0, or alternatively can be registered with the Khronos Group here
- Bound: All IDs for nodes are below this number (should be as small as possible) ( ∀x(x < bound), x ∈ IDs )
- Schema: Instruction schema (if needed). Usually 0.
Here instruction means a node that consists of one operation and zero or more operands, each having zero or more parameters.
Each instruction starts with a slot that encodes the opcode and the length of the instruction in number of slots. The lower 16 bits contains the opcode, while the remaining higher 16 bits contains the length. This is needed in case of quantified operands and helps traversing a module without decoding all instructions.
Some instructions have operands. They can be:
- Literals (string or integer)
- Enums (Bit and Value)
- IDs (target, result or ref)
Some operands require certain capabilities or extensions to be present. These need to be declared at the front of the module. Using these the backend can determine if further compilation is possible (or the target format). E.G. OPCapability Float64
, which means the module uses 64 bit floating-point numbers.
Some operands can be quantified with either '?' (0 or one) or '*' (0 or more). These can be determined by examining the word count of an instruction.
Some operands have parameters. These behave the same as operands except that parameters are never quantified.
The specification of SPIR-V describes a strict order of operations:
- [Required] Capabilities: All
OpCapability
instructions. - [Optional] Extensions: All
OpExtension
- [Optional] External imports: All
OpExtInstImport
(for example opencl.std for built-ins) - [Required] Memory Model: One
OpMemoryModel
instruction - [Required] Entry points: One or more
OpEntryPoint
instructions, which describe the kernels/shaders available in the module (there can be more than one kernel/shader in a single module). In case the Linkage capability is declared, there does not need to be a kernel/shader. - [Optional] Execution Mode: All
OpExecutionMode
orOpExecutionModeId
instructions - [Optional] Debug instructions: These instructions grouped together as follows:
OpString
,OpSourceExtension
,OpSource
,OpSourceContinued
OpName
andOpMemberName
OpModuleProcessed
- [Optional] Annotations: All decorations instructions (
OpDecorate
,OpMemberDecorate
,OpGroupDecorate
,OpGroupMemberDecorate
,OpDecorationGroup
) - [Optional] Type and global variable declarations, constants, : All
OpType{...}
,OpVariable
with storage class other than Function andOpConstant
instructions - [Optional] Function declarations (no-body): In a function declaration the following is required:
- Function declaration
OpFunction
- All parameters using
OpFunctionParameter
- Function end using
OpFunctionEnd
- Function declaration
- [Optional] Function definitions (with body): The only difference to a function declaration is that there is a list of blocks after the function parameters
- Blocks always exist in a function
- Blocks start with an
OpLabel
instruction, so that other blocks can refer to it using the resulting ID. - Blocks end with a termination instruction (Branch instructions(
OpBranch
,OpBranchConditional
,OpSwitch
,OpReturn
,OpReturnValue
),OpKill
,OpUnreachable
) OpVariable
instructions inside a block must have Function as storage class- All
OpVariable
instructions in a function must be the first instructions in the first block of that function (except forOpPhi
, which cannot be in the first block)
OpCapability
: declares a capability that the module uses, and the target device will have to support e.g.OpCapability Addresses
for physical addressingOpExtension
: declares use of an extension to SPIR-V (additional instructions, semantics etc.). e.g.SPV_EXT_physical_storage_buffer
OpExtInstImport
: imports an extended set of instructions. e.g.OpExtInstImport "OpenCL.std"
for OCL built-insOpMemoryModel
: sets addressing model and memory model for the entire module (Logical/Physical32/Physical64 and Simple/OpenCL/GLSL450) e.g.OpMemoryModel Physical32 OpenCL
OpEntryPoint
: declares an entry point, its execution model, and its interface. e.g.OpEntryPoint Kernel %10 "vecAdd" %5
OpExecutionMode
: declare an execution mode for an entry point (LocalSize/LocalSizeHint/VecTypeHint/ContractionOff/Initializer/Finalizer/etc.). e.g.OpExecutionMode %10 ContractionOff
OpFunction
: adds a function, with control (None|Inline|DontInline|Pure|Const) e.g.%10 = OpFunction %6 DontInline %9
OpFunctionParameter
: declares the existence and type of parameter of the current function (must be afterOpFunction
). e.g.%11 = OpFunctionParameter %8