1pub const MAGIC: u32 = 0x0723_0203;
41
42pub const VERSION_1_0: u32 = 0x0001_0000;
51
52pub const GENERATOR: u32 = 0;
54
55pub const OP_NAME: u16 = 5;
58pub const OP_MEMBER_NAME: u16 = 6;
59pub const OP_MEMORY_MODEL: u16 = 14;
60pub const OP_ENTRY_POINT: u16 = 15;
61pub const OP_EXECUTION_MODE: u16 = 16;
62pub const OP_CAPABILITY: u16 = 17;
63pub const OP_TYPE_VOID: u16 = 19;
64pub const OP_TYPE_BOOL: u16 = 20;
65pub const OP_TYPE_INT: u16 = 21;
66pub const OP_TYPE_FLOAT: u16 = 22;
67pub const OP_TYPE_VECTOR: u16 = 23;
68pub const OP_TYPE_RUNTIME_ARRAY: u16 = 29;
69pub const OP_TYPE_STRUCT: u16 = 30;
70pub const OP_TYPE_POINTER: u16 = 32;
71pub const OP_TYPE_FUNCTION: u16 = 33;
72pub const OP_CONSTANT: u16 = 43;
73pub const OP_FUNCTION: u16 = 54;
74pub const OP_FUNCTION_END: u16 = 56;
75pub const OP_VARIABLE: u16 = 59;
76pub const OP_LOAD: u16 = 61;
77pub const OP_STORE: u16 = 62;
78pub const OP_ACCESS_CHAIN: u16 = 65;
79pub const OP_DECORATE: u16 = 71;
80pub const OP_MEMBER_DECORATE: u16 = 72;
81pub const OP_COMPOSITE_EXTRACT: u16 = 81;
82pub const OP_CONVERT_S_TO_F: u16 = 111;
83pub const OP_CONVERT_U_TO_F: u16 = 112;
84pub const OP_BITCAST: u16 = 124;
85pub const OP_F_NEGATE: u16 = 127;
86pub const OP_I_ADD: u16 = 128;
87pub const OP_F_ADD: u16 = 129;
88pub const OP_I_SUB: u16 = 130;
89pub const OP_I_MUL: u16 = 132;
90pub const OP_F_MUL: u16 = 133;
91pub const OP_SELECT: u16 = 169;
92pub const OP_I_EQUAL: u16 = 170;
93pub const OP_I_NOT_EQUAL: u16 = 171;
94pub const OP_U_LESS_THAN: u16 = 176;
95pub const OP_SHIFT_RIGHT_LOGICAL: u16 = 194;
96pub const OP_SHIFT_LEFT_LOGICAL: u16 = 196;
97pub const OP_BITWISE_OR: u16 = 197;
98pub const OP_BITWISE_XOR: u16 = 198;
99pub const OP_BITWISE_AND: u16 = 199;
100pub const OP_LOOP_MERGE: u16 = 246;
101pub const OP_SELECTION_MERGE: u16 = 247;
102pub const OP_LABEL: u16 = 248;
103pub const OP_BRANCH: u16 = 249;
104pub const OP_BRANCH_CONDITIONAL: u16 = 250;
105pub const OP_RETURN: u16 = 253;
106
107pub const CAP_SHADER: u32 = 1;
110pub const ADDRESSING_LOGICAL: u32 = 0;
111pub const MEMORY_MODEL_GLSL450: u32 = 1;
112pub const EXEC_MODEL_GL_COMPUTE: u32 = 5;
113pub const EXEC_MODE_LOCAL_SIZE: u32 = 17;
114
115pub const SC_INPUT: u32 = 1;
116pub const SC_UNIFORM: u32 = 2;
117pub const SC_FUNCTION: u32 = 7;
118pub const SC_PUSH_CONSTANT: u32 = 9;
119
120pub const DEC_BLOCK: u32 = 2;
121pub const DEC_BUFFER_BLOCK: u32 = 3;
122pub const DEC_ARRAY_STRIDE: u32 = 6;
123pub const DEC_BUILTIN: u32 = 11;
124pub const DEC_BINDING: u32 = 33;
125pub const DEC_DESCRIPTOR_SET: u32 = 34;
126pub const DEC_OFFSET: u32 = 35;
127
128pub const BUILTIN_GLOBAL_INVOCATION_ID: u32 = 28;
129
130pub const NONE: u32 = 0;
131
132#[derive(Clone, Copy, PartialEq, Eq, Debug)]
139pub enum Section {
140 Prelude,
142 Debug,
146 Annotations,
148 Types,
150 Code,
152}
153
154#[derive(Default)]
156pub struct Builder {
157 next_id: u32,
158 prelude: Vec<u32>,
159 debug: Vec<u32>,
160 annotations: Vec<u32>,
161 types: Vec<u32>,
162 code: Vec<u32>,
163}
164
165impl Builder {
166 pub fn new() -> Self {
167 Self {
168 next_id: 1,
169 ..Default::default()
170 }
171 }
172
173 pub fn id(&mut self) -> u32 {
175 let id = self.next_id;
176 self.next_id += 1;
177 id
178 }
179
180 fn section(&mut self, s: Section) -> &mut Vec<u32> {
181 match s {
182 Section::Prelude => &mut self.prelude,
183 Section::Debug => &mut self.debug,
184 Section::Annotations => &mut self.annotations,
185 Section::Types => &mut self.types,
186 Section::Code => &mut self.code,
187 }
188 }
189
190 pub fn inst(&mut self, s: Section, op: u16, operands: &[u32]) {
192 let out = self.section(s);
193 push_inst(out, op, operands);
194 }
195
196 pub fn typed(&mut self, s: Section, op: u16, result_type: u32, rest: &[u32]) -> u32 {
199 let id = self.id();
200 let mut ops = vec![result_type, id];
201 ops.extend_from_slice(rest);
202 self.inst(s, op, &ops);
203 id
204 }
205
206 pub fn result(&mut self, s: Section, op: u16, rest: &[u32]) -> u32 {
209 let id = self.id();
210 let mut ops = vec![id];
211 ops.extend_from_slice(rest);
212 self.inst(s, op, &ops);
213 id
214 }
215
216 pub fn inst_str(&mut self, s: Section, op: u16, head: &[u32], text: &str, tail: &[u32]) {
219 let mut ops = head.to_vec();
220 ops.extend(encode_string(text));
221 ops.extend_from_slice(tail);
222 self.inst(s, op, &ops);
223 }
224
225 pub fn finish(self) -> Vec<u32> {
228 let mut out = Vec::with_capacity(
229 5 + self.prelude.len()
230 + self.debug.len()
231 + self.annotations.len()
232 + self.types.len()
233 + self.code.len(),
234 );
235 out.push(MAGIC);
236 out.push(VERSION_1_0);
237 out.push(GENERATOR);
238 out.push(self.next_id);
240 out.push(0); out.extend_from_slice(&self.prelude);
242 out.extend_from_slice(&self.debug);
243 out.extend_from_slice(&self.annotations);
244 out.extend_from_slice(&self.types);
245 out.extend_from_slice(&self.code);
246 out
247 }
248}
249
250fn push_inst(out: &mut Vec<u32>, op: u16, operands: &[u32]) {
251 let word_count = operands.len() + 1;
252 assert!(
253 word_count < 0x1_0000,
254 "SPIR-V instruction longer than the 16-bit word count field"
255 );
256 out.push(((word_count as u32) << 16) | op as u32);
257 out.extend_from_slice(operands);
258}
259
260pub fn encode_string(text: &str) -> Vec<u32> {
263 let mut bytes = text.as_bytes().to_vec();
264 bytes.push(0);
265 while !bytes.len().is_multiple_of(4) {
266 bytes.push(0);
267 }
268 let (words, rest) = bytes.as_chunks::<4>();
272 debug_assert!(rest.is_empty(), "padding loop left a partial word");
273 words.iter().map(|c| u32::from_le_bytes(*c)).collect()
274}
275
276pub fn to_bytes(words: &[u32]) -> Vec<u8> {
279 let mut out = Vec::with_capacity(words.len() * 4);
280 for w in words {
281 out.extend_from_slice(&w.to_le_bytes());
282 }
283 out
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn header_is_well_formed_and_bound_covers_every_id() {
292 let mut b = Builder::new();
293 let a = b.id();
294 let c = b.id();
295 assert_eq!((a, c), (1, 2));
296 let words = b.finish();
297 assert_eq!(words[0], MAGIC);
298 assert_eq!(words[1], VERSION_1_0);
299 assert_eq!(words[3], 3, "bound must be one past the largest id");
300 assert_eq!(words[4], 0);
301 assert_eq!(words.len(), 5, "no sections were written");
302 }
303
304 #[test]
305 fn instruction_word_count_is_operands_plus_one() {
306 let mut b = Builder::new();
307 b.inst(Section::Prelude, OP_CAPABILITY, &[CAP_SHADER]);
308 let words = b.finish();
309 assert_eq!(words[5], (2 << 16) | OP_CAPABILITY as u32);
310 assert_eq!(words[6], CAP_SHADER);
311 }
312
313 #[test]
314 fn sections_are_concatenated_in_layout_order_regardless_of_write_order() {
315 let mut b = Builder::new();
316 b.inst(Section::Code, OP_RETURN, &[]);
318 b.inst(Section::Annotations, OP_DECORATE, &[1, DEC_BLOCK]);
319 b.inst(Section::Prelude, OP_CAPABILITY, &[CAP_SHADER]);
320 let words = b.finish();
321 let ops: Vec<u16> = decode_opcodes(&words[5..]);
322 assert_eq!(ops, vec![OP_CAPABILITY, OP_DECORATE, OP_RETURN]);
323 }
324
325 #[test]
326 fn strings_are_nul_terminated_and_word_padded() {
327 assert_eq!(encode_string("main").len(), 2);
329 assert_eq!(encode_string("main")[1], 0);
330 assert_eq!(encode_string("abc").len(), 1);
332 assert_eq!(encode_string("abc")[0], 0x0063_6261);
333 assert_eq!(encode_string("").len(), 1);
334 assert_eq!(encode_string("")[0], 0);
335 }
336
337 #[test]
338 fn to_bytes_is_little_endian() {
339 assert_eq!(to_bytes(&[MAGIC]), vec![0x03, 0x02, 0x23, 0x07]);
340 }
341
342 pub(crate) fn decode_opcodes(words: &[u32]) -> Vec<u16> {
345 let mut ops = Vec::new();
346 let mut i = 0;
347 while i < words.len() {
348 let count = (words[i] >> 16) as usize;
349 assert!(count > 0, "zero-length instruction at word {i}");
350 ops.push((words[i] & 0xffff) as u16);
351 i += count;
352 }
353 assert_eq!(i, words.len(), "instruction stream overran the module");
354 ops
355 }
356}