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
OpCapabilityinstructions. - [Optional] Extensions: All
OpExtension - [Optional] External imports: All
OpExtInstImport(for example opencl.std for built-ins) - [Required] Memory Model: One
OpMemoryModelinstruction - [Required] Entry points: One or more
OpEntryPointinstructions, 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
OpExecutionModeorOpExecutionModeIdinstructions - [Optional] Debug instructions: These instructions grouped together as follows:
OpString,OpSourceExtension,OpSource,OpSourceContinuedOpNameandOpMemberNameOpModuleProcessed
- [Optional] Annotations: All decorations instructions (
OpDecorate,OpMemberDecorate,OpGroupDecorate,OpGroupMemberDecorate,OpDecorationGroup) - [Optional] Type and global variable declarations, constants, : All
OpType{...},OpVariablewith storage class other than Function andOpConstantinstructions - [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
OpLabelinstruction, 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) OpVariableinstructions inside a block must have Function as storage class- All
OpVariableinstructions 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 Addressesfor physical addressingOpExtension: declares use of an extension to SPIR-V (additional instructions, semantics etc.). e.g.SPV_EXT_physical_storage_bufferOpExtInstImport: 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 OpenCLOpEntryPoint: declares an entry point, its execution model, and its interface. e.g.OpEntryPoint Kernel %10 "vecAdd" %5OpExecutionMode: declare an execution mode for an entry point (LocalSize/LocalSizeHint/VecTypeHint/ContractionOff/Initializer/Finalizer/etc.). e.g.OpExecutionMode %10 ContractionOffOpFunction: adds a function, with control (None|Inline|DontInline|Pure|Const) e.g.%10 = OpFunction %6 DontInline %9OpFunctionParameter: declares the existence and type of parameter of the current function (must be afterOpFunction). e.g.%11 = OpFunctionParameter %8