oxicuda_levelzero/
spirv.rs1pub const SPIRV_MAGIC: u32 = 0x07230203;
31pub const SPIRV_VERSION_1_2: u32 = 0x0001_0200;
33pub const SPIRV_GENERATOR: u32 = 0x000D_0002;
35
36pub struct SpvModule {
43 words: Vec<u32>,
44 id_bound: u32,
46}
47
48impl SpvModule {
49 pub fn new() -> Self {
51 let words = vec![
53 SPIRV_MAGIC,
54 SPIRV_VERSION_1_2,
55 SPIRV_GENERATOR,
56 0, 0, ];
59 Self { words, id_bound: 1 }
60 }
61
62 pub fn alloc_id(&mut self) -> u32 {
64 let id = self.id_bound;
65 self.id_bound += 1;
66 id
67 }
68
69 pub fn emit(&mut self, opcode: u32, operands: &[u32]) {
73 let word_count = (1 + operands.len()) as u32;
74 self.words.push((word_count << 16) | opcode);
75 self.words.extend_from_slice(operands);
76 }
77
78 pub fn string_words(s: &str) -> Vec<u32> {
80 let bytes = s.as_bytes();
81 let padded_len = (bytes.len() + 4) & !3;
83 let mut out = vec![0u32; padded_len / 4];
84 for (i, &b) in bytes.iter().enumerate() {
85 let word_idx = i / 4;
86 let byte_idx = i % 4;
87 out[word_idx] |= (b as u32) << (byte_idx * 8);
88 }
89 out
90 }
91
92 pub fn finalize(mut self) -> Vec<u32> {
94 self.words[3] = self.id_bound;
95 self.words
96 }
97}
98
99impl Default for SpvModule {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105const OP_CAPABILITY: u32 = 17;
108const OP_MEMORY_MODEL: u32 = 14;
109const OP_ENTRY_POINT: u32 = 15;
110const OP_EXECUTION_MODE: u32 = 16;
111const OP_TYPE_VOID: u32 = 19;
112const OP_TYPE_FUNCTION: u32 = 33;
113const OP_FUNCTION: u32 = 54;
114const OP_LABEL: u32 = 248;
115const OP_RETURN: u32 = 253;
116const OP_FUNCTION_END: u32 = 56;
117
118const CAPABILITY_SHADER: u32 = 1;
120const ADDRESSING_MODEL_LOGICAL: u32 = 0;
122const MEMORY_MODEL_GLSL450: u32 = 1;
123const EXECUTION_MODEL_GLCOMPUTE: u32 = 5;
125const EXECUTION_MODE_LOCAL_SIZE: u32 = 17;
127const FUNCTION_CONTROL_NONE: u32 = 0;
129
130pub fn trivial_compute_shader() -> Vec<u32> {
135 let mut m = SpvModule::new();
136
137 let id_main_fn = m.alloc_id(); let id_void = m.alloc_id(); let id_void_fn = m.alloc_id(); let id_label = m.alloc_id(); m.emit(OP_CAPABILITY, &[CAPABILITY_SHADER]);
147
148 m.emit(
150 OP_MEMORY_MODEL,
151 &[ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450],
152 );
153
154 let mut entry_words = vec![EXECUTION_MODEL_GLCOMPUTE, id_main_fn];
157 entry_words.extend(SpvModule::string_words("main"));
158 m.emit(OP_ENTRY_POINT, &entry_words);
159
160 m.emit(
162 OP_EXECUTION_MODE,
163 &[id_main_fn, EXECUTION_MODE_LOCAL_SIZE, 1, 1, 1],
164 );
165
166 m.emit(OP_TYPE_VOID, &[id_void]);
170
171 m.emit(OP_TYPE_FUNCTION, &[id_void_fn, id_void]);
173
174 m.emit(
178 OP_FUNCTION,
179 &[id_void, id_main_fn, FUNCTION_CONTROL_NONE, id_void_fn],
180 );
181
182 m.emit(OP_LABEL, &[id_label]);
184
185 m.emit(OP_RETURN, &[]);
187
188 m.emit(OP_FUNCTION_END, &[]);
190
191 m.finalize()
192}
193
194pub fn trivial_compute_shader_bytes() -> Vec<u8> {
200 trivial_compute_shader()
201 .iter()
202 .flat_map(|w| w.to_ne_bytes())
203 .collect()
204}
205
206#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn placeholder_spv_valid_magic() {
214 let words = trivial_compute_shader();
215 assert!(!words.is_empty(), "SPIR-V module must not be empty");
216 assert_eq!(words[0], SPIRV_MAGIC, "first word must be SPIR-V magic");
217 }
218
219 #[test]
220 fn placeholder_spv_word_aligned() {
221 let bytes = trivial_compute_shader_bytes();
222 assert_eq!(bytes.len() % 4, 0, "SPIR-V must be 4-byte aligned");
223 }
224
225 #[test]
226 fn placeholder_spv_version_and_schema() {
227 let words = trivial_compute_shader();
228 assert!(words.len() >= 5, "header must have 5 words");
229 assert!(words[1] >= 0x0001_0000, "SPIR-V version must be >= 1.0");
231 assert_eq!(words[4], 0, "schema word must be 0");
232 }
233
234 #[test]
235 fn placeholder_spv_nonzero_bound() {
236 let words = trivial_compute_shader();
237 assert!(words[3] > 0, "ID bound must be > 0 when IDs are allocated");
238 }
239
240 #[test]
241 fn spv_module_id_allocation_is_monotonic() {
242 let mut m = SpvModule::new();
243 let id1 = m.alloc_id();
244 let id2 = m.alloc_id();
245 assert!(id2 > id1);
246 }
247
248 #[test]
249 fn string_words_null_terminated() {
250 let words = SpvModule::string_words("abc");
251 assert!(!words.is_empty());
253 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
255 assert_eq!(bytes[0], b'a');
257 assert_eq!(bytes[1], b'b');
258 assert_eq!(bytes[2], b'c');
259 assert_eq!(bytes[3], 0);
260 }
261
262 #[test]
263 fn string_words_empty_string() {
264 let words = SpvModule::string_words("");
265 assert!(!words.is_empty());
267 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
269 assert_eq!(bytes[0], 0);
270 }
271
272 #[test]
273 fn generator_magic_is_level_zero() {
274 assert_eq!(SPIRV_GENERATOR, 0x000D_0002);
276 assert_ne!(SPIRV_GENERATOR, 0x000D_0001);
277 }
278}