SPIRV-Tools/syntax.md
2015-10-26 12:55:33 -04:00

6.6 KiB

SPIR-V Assembly language syntax

The assembly attempts to adhere to the binary form as closely as possible using text names from that specification. Here is an example.

OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %3 "main"
OpExecutionMode %3 LocalSize 64 64 1
OpTypeVoid %1
OpTypeFunction %2 %1
OpFunction %1 %3 None %2
OpLabel %4
OpReturn
OpFunctionEnd

In order to improve the text's readability, the <result-id> generated by an instruction can be moved to the beginning of that instruction and followed by an = sign. This allows us to distinguish between variable defs and uses and locate variable defs more easily. So, the above example can also be written as:

     OpCapability Shader
     OpMemoryModel Logical Simple
     OpEntryPoint GLCompute %3 "main"
     OpExecutionMode %3 LocalSize 64 64 1
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
     OpReturn
     OpFunctionEnd

Each line encapsulates one and only one instruction, or an OpCode and all of its operands. OpCodes use the names provided in section 3.28 Instructions of the SPIR-V specification, immediate values such as Addressing Model, Memory Model, etc. use the names provided in sections 3.2 Source Language through 3.27 Capability of the SPIR-V specification. Literals strings are enclosed in quotes "<string>" while literal numbers have no special formatting.

ID Definitions & Usage

An ID definition pertains to the <result-id> of an OpCode, and ID usage is any input to an OpCode. All IDs are prefixed with %. To differentiate between defs and uses, we suggest using the second format shown in the above example.

Named IDs

The assembler also supports named IDs, or virtual IDs, which greatly improves the readability of the assembly. The same ID definition and usage prefixes apply. Names must begin with an character in the range [a-z|A-Z]. The following example will result in identical SPIR-V binary as the example above.

          OpCapability Shader
          OpMemoryModel Logical Simple
          OpEntryPoint GLCompute %main "main"
          OpExecutionMode %main LocalSize 64 64 1
  %void = OpTypeVoid
%fnMain = OpTypeFunction %void
  %main = OpFunction %void None %fnMain
%lbMain = OpLabel
          OpReturn
          OpFunctionEnd

Arbitrary Integers

Warning: Not all of the following has been implemented

When writing tests it can be useful to emit an invalid 32 bit word into the binary stream at arbitrary positions within the assembly. To specify an arbitrary word into the stream the prefix ! is used, this takes the form !<integer>. Here is an example.

OpCapability !0x0000FF00

Any word in a valid assembly program may be replaced by !<integer> -- even words that dictate how the rest of the instruction is parsed. Consider, for example, the following assembly program:

%4 = OpConstant %1 123 456 789 OpExecutionMode %2 LocalSize 11 22 33
OpExecutionMode %3 InputLines

The words OpConstant, LocalSize, and InputLines may be replaced by random !<integer> values, and the assembler will still assemble an output binary with three instructions. It will not necessarily be valid SPIR-V, but it will faithfully reflect the input text.

You may wonder how the assembler recognizes the instruction structure (including instruction boundaries) in the text with certain crucial words replaced by arbitrary integers. If, say, OpConstant becomes a !<integer> whose value differs from the binary representation of OpConstant (remember that this feature is intended for fine-grain control in SPIR-V testing), the assembler generally has no idea what that value stands for. So how does it know there is exactly one <id> and three number literals following in that instruction, before the next one begins? And if LocalSize is replaced by an arbitrary !<integer>, how does it know to take the next three words (instead of zero or one, both of which are possible in the absence of certainty that LocalSize provided)? The answer is a simple rule governing the parsing of instructions with !<integer> in them:

When a word in the assembly program is a !<integer>, that integer value is emitted into the binary output, and parsing proceeds differently than before: each subsequent word not recognized as an OpCode is emitted into the binary output without any checking; when a recognizable OpCode is eventually encountered, it begins a new instruction and parsing returns to normal. (If a subsequent OpCode is never found, then this alternate parsing mode handles all the remaining words in the program. If a subsequent OpCode is in an assignment format, the ID preceding it begins a new instruction, even if that ID is itself a !<integer>.)

The assembler processes the words encountered in alternate parsing mode as follows:

  • If the word is a number literal, it outputs that number as one or more words, as defined in the SPIR-V specification for Literal Number.
  • If the word is a string literal, it outputs a sequence of words representing the string as defined in the SPIR-V specification for Literal String.
  • If the word is an ID, it outputs the ID's internal number. If no such number exists yet, a unique new one will be generated. (Uniqueness is at the translation-unit level: no other ID in the same translation unit will have the same number.)
  • If the word is another !<integer>, it outputs that integer.
  • Any other word causes the assembler to quit with an error.

Note that this has some interesting consequences, including:

  • When an OpCode is replaced by !<integer>, the integer value should encode the instruction's word count, as specified in the physical-layout section of the SPIR-V specification.

  • Consecutive instructions may have their OpCode replaced by !<integer> and still produce valid SPIR-V. For example, !262187 %1 %2 "abc" !327739 %1 %3 6 %2 will successfully assemble into SPIR-V declaring a constant and a PrivateGlobal variable.

  • Enums (such as DontInline or SubgroupMemory, for instance) are not handled by the alternate parsing mode. They must be replaced by !<integer> for successful assembly.

  • The = sign cannot be processed by the alternate parsing mode if the OpCode following it is a !<integer>.

  • When replacing a named ID with !<integer>, it is possible to generate unintentionally valid SPIR-V. If the integer provided happens to equal a number generated for an existing named ID, it will result in a reference to that named ID being output. This may be valid SPIR-V, contrary to the presumed intention of the writer.