aboutsummaryrefslogtreecommitdiff
path: root/syntax.md
blob: be3a5d5ad11640041c4184b7d632245e2131cdef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# SPIR-V Assembly language syntax

## Overview

The assembly attempts to adhere to the binary form from Section 3 of the SPIR-V
spec as closely as possible, with one exception aiming at improving the text's
readability.  The `<result-id>` generated by an instruction is moved to the
beginning of that instruction and followed by an `=` sign.  This allows us to
distinguish between variable definitions and uses and locate value definitions
more easily.

Here is an example:

```
     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
```

A module is a sequence of instructions, separated by whitespace.
An instruction is an opcode name followed by operands, separated by
whitespace.  Typically each instruction is presented on its own line,
but the assembler does not enforce this rule.

The opcode names and expected operands are described in Section 3 of
the SPIR-V specification.  An operand is one of:
* a literal integer: A decimal integer, or a hexadecimal integer.
  A hexadecimal integer is indicated by a leading `0x` or `0X`.  A hex
  integer supplied for a signed integer value will be sign-extended.
  For example, `0xffff` supplied as the literal for an `OpConstant`
  on a signed 16-bit integer type will be interpreted as the value `-1`.
* a literal floating point number, in decimal or hexadecimal form.
  See [below](#floats).
* a literal string.
   * A literal string is everything following a double-quote `"` until the
     following un-escaped double-quote. This includes special characters such
     as newlines.
   * A backslash `\` may be used to escape characters in the string. The `\`
     may be used to escape a double-quote or a `\` but is simply ignored when
     preceding any other character.
* a named enumerated value, specific to that operand position.  For example,
  the `OpMemoryModel` takes a named Addressing Model operand (e.g. `Logical` or
  `Physical32`), and a named Memory Model operand (e.g. `Simple` or `OpenCL`).
  Named enumerated values are only meaningful in specific positions, and will
  otherwise generate an error.
* a mask expression, consisting of one or more mask enum names separated
  by `|`.  For example, the expression `NotNaN|NotInf|NSZ` denotes the mask
  which is the combination of the `NotNaN`, `NotInf`, and `NSZ` flags.
* an injected immediate integer: `!<integer>`.  See [below](#immediate).
* an ID, e.g. `%foo`. See [below](#id).
* the name of an extended instruction.  For example, `sqrt` in an extended
  instruction such as `%f = OpExtInst %f32 %OpenCLImport sqrt %arg`
* the name of an opcode for OpSpecConstantOp, but where the `Op` prefix
  is removed.  For example, the following indicates the use of an integer
  addition in a specialization constant computation:
  `%sum = OpSpecConstantOp %i32 IAdd %a %b`

## ID Definitions & Usage
<a name="id"></a>

An ID _definition_ pertains to the `<result-id>` of an instruction, and ID
_usage_ is a use of an ID as an input to an instruction.

An ID in the assembly language begins with `%` and must be followed by a name
consisting of one or more letters, numbers or underscore characters.

For every ID in the assembly program, the assembler generates a unique number
called the ID's internal number. Then each ID reference translates into its
internal number in the SPIR-V output. Internal numbers are unique within the
compilation unit: no two IDs in the same unit will share internal numbers.

The disassembler generates IDs where the name is always a decimal number
greater than 0.

So the example can be rewritten using more user-friendly names, as follows:
```
          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
```

## Floating point literals
<a name="floats"></a>

The assembler and disassembler support floating point literals in both
decimal and hexadecimal form.

The syntax for a floating point literal is the same as floating point
constants in the C programming language, except:
* An optional leading minus (`-`) is part of the literal.
* An optional type specifier suffix is not allowed.
Infinity and NaN values are expressed in hexadecimal float literals
by using the maximum representable exponent for the bit width.

For example, in 32-bit floating point, 8 bits are used for the exponent, and the
exponent bias is 127.  So the maximum representable unbiased exponent is 128.
Therefore, we represent the infinities and some NaNs as follows:

```
%float32 = OpTypeFloat 32
%inf     = OpConstant %float32 0x1p+128
%neginf  = OpConstant %float32 -0x1p+128
%aNaN    = OpConstant %float32 0x1.8p+128
%moreNaN = OpConstant %float32 -0x1.0002p+128
```
The assembler preserves all the bits of a NaN value.  For example, the encoding
of `%aNaN` in the previous example is the same as the word with bits
`0x7fc00000`, and `%moreNaN` is encoded as `0xff800100`.

The disassembler prints infinite, NaN, and subnormal values in hexadecimal form.
Zero and normal values are printed in decimal form with enough digits
to preserve all significand bits.

## Arbitrary Integers
<a name="immediate"></a>

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 token in a valid assembly program may be replaced by `!<integer>` -- even
tokens 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 tokens `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 tokens 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 tokens (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 token in the assembly program is a `!<integer>`, that integer value is
emitted into the binary output, and parsing proceeds differently than before:
each subsequent token not recognized as an OpCode or a <result-id> is emitted
into the binary output without any checking; when a recognizable OpCode or a
<result-id> 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 tokens in the program.)

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

* If the token is a number literal, since context may be lost, the number
  is interpreted as a 32-bit value and output as a single word.  In order to
  specify multiple-word literals in alternate-parsing mode, further uses of
  `!<integer>` tokens may be required.
  All formats supported by `strtoul()` are accepted.
* If the token 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 token is an ID, it outputs the ID's internal number.
* If the token is another `!<integer>`, it outputs that integer.
* Any other token 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 `<result-id>` on the left-hand side of an assignment cannot be a
  `!<integer>`. The `<result-id>` can be still be manually controlled if desired
  by expressing the entire instruction as `!<integer>` tokens for its opcode and
  operands.

* 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.

## Notes

* Some enumerants cannot be used by name, because the target instruction
in which they are meaningful take an ID reference instead of a literal value.
For example:
   * Named enumerated value `CmdExecTime` from section 3.30 Kernel
     Profiling Info is used in constructing a mask value supplied as
     an ID for `OpCaptureEventProfilingInfo`.  But no other instruction
     has enough context to bring the enumerant names from section 3.30
     into scope.
   * Similarly, the names in section 3.29 Kernel Enqueue Flags are used to
     construct a value supplied as an ID to the Flags argument of
     OpEnqueueKernel.
   * Similarly for the names in section 3.25 Memory Semantics.
   * Similarly for the names in section 3.27 Scope.
* Some enumerants cannot be used by name, because they only name values
returned by an instruction:
   * Enumerants from 3.12 Image Channel Order name possible values returned
     by the `OpImageQueryOrder` instruction.
   * Enumerants from 3.13 Image Channel Data Type name possible values
     returned by the `OpImageQueryFormat` instruction.