1use oxicuda_backend::{BinaryOp, ReduceOp, UnaryOp};
17
18pub const SPIRV_MAGIC: u32 = 0x07230203;
22pub const SPIRV_VERSION_1_2: u32 = 0x0001_0200;
24pub const SPIRV_GENERATOR: u32 = 0x000D_0002;
26
27pub(crate) const OP_EXTENSION: u32 = 10;
30pub(crate) const OP_EXT_INST_IMPORT: u32 = 11;
31pub(crate) const OP_EXT_INST: u32 = 12;
32pub(crate) const OP_MEMORY_MODEL: u32 = 14;
33pub(crate) const OP_ENTRY_POINT: u32 = 15;
34pub(crate) const OP_EXECUTION_MODE: u32 = 16;
35pub(crate) const OP_CAPABILITY: u32 = 17;
36pub(crate) const OP_TYPE_VOID: u32 = 19;
37pub(crate) const OP_TYPE_BOOL: u32 = 20;
38pub(crate) const OP_TYPE_INT: u32 = 21;
39pub(crate) const OP_TYPE_FLOAT: u32 = 22;
40pub(crate) const OP_TYPE_VECTOR: u32 = 23;
41pub(crate) const OP_TYPE_ARRAY: u32 = 28;
42pub(crate) const OP_TYPE_POINTER: u32 = 32;
43pub(crate) const OP_TYPE_FUNCTION: u32 = 33;
44pub(crate) const OP_CONSTANT: u32 = 43;
45pub(crate) const OP_FUNCTION: u32 = 54;
46pub(crate) const OP_FUNCTION_PARAMETER: u32 = 55;
47pub(crate) const OP_FUNCTION_END: u32 = 56;
48pub(crate) const OP_VARIABLE: u32 = 59;
49pub(crate) const OP_LOAD: u32 = 61;
50pub(crate) const OP_STORE: u32 = 62;
51pub(crate) const OP_IN_BOUNDS_PTR_ACCESS_CHAIN: u32 = 70;
52pub(crate) const OP_DECORATE: u32 = 71;
53pub(crate) const OP_COMPOSITE_EXTRACT: u32 = 81;
54pub(crate) const OP_CONVERT_U_TO_F: u32 = 112;
55pub(crate) const OP_F_NEGATE: u32 = 127;
56pub(crate) const OP_I_ADD: u32 = 128;
57pub(crate) const OP_F_ADD: u32 = 129;
58pub(crate) const OP_F_SUB: u32 = 131;
59pub(crate) const OP_I_MUL: u32 = 132;
60pub(crate) const OP_F_MUL: u32 = 133;
61pub(crate) const OP_U_DIV: u32 = 134;
62pub(crate) const OP_F_DIV: u32 = 136;
63pub(crate) const OP_U_MOD: u32 = 137;
64pub(crate) const OP_U_LESS_THAN: u32 = 176;
65pub(crate) const OP_LOOP_MERGE: u32 = 246;
66pub(crate) const OP_SELECTION_MERGE: u32 = 247;
67pub(crate) const OP_LABEL: u32 = 248;
68pub(crate) const OP_BRANCH: u32 = 249;
69pub(crate) const OP_BRANCH_CONDITIONAL: u32 = 250;
70pub(crate) const OP_CONTROL_BARRIER: u32 = 224;
71pub(crate) const OP_PHI: u32 = 245;
72pub(crate) const OP_RETURN: u32 = 253;
73
74pub(crate) const OP_GROUP_NON_UNIFORM_FADD: u32 = 350;
76pub(crate) const OP_GROUP_NON_UNIFORM_SHUFFLE: u32 = 345;
77
78const CAPABILITY_SHADER: u32 = 1;
80const CAPABILITY_ADDRESSES: u32 = 4;
81const CAPABILITY_KERNEL: u32 = 6;
82
83const ADDRESSING_MODEL_LOGICAL: u32 = 0;
85const ADDRESSING_MODEL_PHYSICAL64: u32 = 2;
86const MEMORY_MODEL_GLSL450: u32 = 1;
87const MEMORY_MODEL_OPENCL: u32 = 2;
88
89const EXECUTION_MODEL_GLCOMPUTE: u32 = 5;
91pub(crate) const EXECUTION_MODEL_KERNEL: u32 = 6;
92const EXECUTION_MODE_LOCAL_SIZE: u32 = 17;
93
94pub(crate) const FUNCTION_CONTROL_NONE: u32 = 0;
96
97const DECORATION_BUILTIN: u32 = 11;
99
100const BUILTIN_GLOBAL_INVOCATION_ID: u32 = 28;
102
103const STORAGE_CLASS_INPUT: u32 = 1;
105const STORAGE_CLASS_CROSS_WORKGROUP: u32 = 5;
106pub(crate) const STORAGE_CLASS_FUNCTION: u32 = 7;
107
108const SELECTION_CONTROL_NONE: u32 = 0;
110const LOOP_CONTROL_NONE: u32 = 0;
111
112pub(crate) const OPENCL_EXP: u32 = 19;
114const OPENCL_FABS: u32 = 23;
115pub(crate) const OPENCL_FMAX: u32 = 27;
116const OPENCL_FMIN: u32 = 28;
117const OPENCL_LOG: u32 = 37;
118const OPENCL_SQRT: u32 = 61;
119const OPENCL_TANH: u32 = 63;
120const OPENCL_ATAN: u32 = 9;
122const OPENCL_ATAN2: u32 = 11;
123const OPENCL_CBRT: u32 = 14;
124const OPENCL_COS: u32 = 16;
125const OPENCL_ERFC: u32 = 17;
126const OPENCL_ERF: u32 = 18;
127const OPENCL_RSQRT: u32 = 56;
128const OPENCL_SIN: u32 = 57;
129
130pub(crate) const WORKGROUP_SIZE: u32 = 256;
132
133pub struct SpvModule {
140 words: Vec<u32>,
141 id_bound: u32,
143}
144
145impl SpvModule {
146 pub fn new() -> Self {
148 let words = vec![SPIRV_MAGIC, SPIRV_VERSION_1_2, SPIRV_GENERATOR, 0, 0];
149 Self { words, id_bound: 1 }
150 }
151
152 pub fn alloc_id(&mut self) -> u32 {
154 let id = self.id_bound;
155 self.id_bound += 1;
156 id
157 }
158
159 pub fn emit(&mut self, opcode: u32, operands: &[u32]) {
161 let word_count = (1 + operands.len()) as u32;
162 self.words.push((word_count << 16) | opcode);
163 self.words.extend_from_slice(operands);
164 }
165
166 pub fn string_words(s: &str) -> Vec<u32> {
168 let bytes = s.as_bytes();
169 let padded_len = (bytes.len() + 4) & !3;
170 let mut out = vec![0u32; padded_len / 4];
171 for (i, &b) in bytes.iter().enumerate() {
172 out[i / 4] |= (b as u32) << ((i % 4) * 8);
173 }
174 out
175 }
176
177 pub fn finalize(mut self) -> Vec<u32> {
179 self.words[3] = self.id_bound;
180 self.words
181 }
182
183 pub(crate) fn emit_capability(&mut self, cap: u32) {
186 self.emit(OP_CAPABILITY, &[cap]);
187 }
188
189 pub(crate) fn emit_ext_inst_import(&mut self, id: u32, name: &str) {
190 let mut ops = vec![id];
191 ops.extend(Self::string_words(name));
192 self.emit(OP_EXT_INST_IMPORT, &ops);
193 }
194
195 pub(crate) fn emit_extension(&mut self, name: &str) {
197 let ops = Self::string_words(name);
198 self.emit(OP_EXTENSION, &ops);
199 }
200
201 pub(crate) fn emit_memory_model(&mut self, addressing: u32, memory: u32) {
202 self.emit(OP_MEMORY_MODEL, &[addressing, memory]);
203 }
204
205 pub(crate) fn emit_entry_point(
206 &mut self,
207 model: u32,
208 func_id: u32,
209 name: &str,
210 interfaces: &[u32],
211 ) {
212 let mut ops = vec![model, func_id];
213 ops.extend(Self::string_words(name));
214 ops.extend_from_slice(interfaces);
215 self.emit(OP_ENTRY_POINT, &ops);
216 }
217
218 pub(crate) fn emit_execution_mode_local_size(&mut self, func_id: u32, x: u32, y: u32, z: u32) {
219 self.emit(
220 OP_EXECUTION_MODE,
221 &[func_id, EXECUTION_MODE_LOCAL_SIZE, x, y, z],
222 );
223 }
224
225 pub(crate) fn emit_decorate(&mut self, target: u32, decoration: u32, operands: &[u32]) {
226 let mut ops = vec![target, decoration];
227 ops.extend_from_slice(operands);
228 self.emit(OP_DECORATE, &ops);
229 }
230
231 pub(crate) fn emit_type_void(&mut self, id: u32) {
232 self.emit(OP_TYPE_VOID, &[id]);
233 }
234
235 pub(crate) fn emit_type_bool(&mut self, id: u32) {
236 self.emit(OP_TYPE_BOOL, &[id]);
237 }
238
239 pub(crate) fn emit_type_int(&mut self, id: u32, width: u32, signedness: u32) {
240 self.emit(OP_TYPE_INT, &[id, width, signedness]);
241 }
242
243 pub(crate) fn emit_type_float(&mut self, id: u32, width: u32) {
244 self.emit(OP_TYPE_FLOAT, &[id, width]);
245 }
246
247 pub(crate) fn emit_type_vector(&mut self, id: u32, component: u32, count: u32) {
248 self.emit(OP_TYPE_VECTOR, &[id, component, count]);
249 }
250
251 pub(crate) fn emit_type_pointer(&mut self, id: u32, storage_class: u32, pointee: u32) {
252 self.emit(OP_TYPE_POINTER, &[id, storage_class, pointee]);
253 }
254
255 pub(crate) fn emit_type_function(&mut self, id: u32, return_type: u32, params: &[u32]) {
256 let mut ops = vec![id, return_type];
257 ops.extend_from_slice(params);
258 self.emit(OP_TYPE_FUNCTION, &ops);
259 }
260
261 pub(crate) fn emit_constant_u32(&mut self, ty: u32, id: u32, value: u32) {
262 self.emit(OP_CONSTANT, &[ty, id, value]);
263 }
264
265 pub(crate) fn emit_constant_f32(&mut self, ty: u32, id: u32, value: f32) {
266 self.emit(OP_CONSTANT, &[ty, id, value.to_bits()]);
267 }
268
269 pub(crate) fn emit_variable(&mut self, ty: u32, id: u32, storage_class: u32) {
270 self.emit(OP_VARIABLE, &[ty, id, storage_class]);
271 }
272
273 pub(crate) fn emit_load(&mut self, result_ty: u32, result: u32, pointer: u32) {
274 self.emit(OP_LOAD, &[result_ty, result, pointer]);
275 }
276
277 pub(crate) fn emit_store(&mut self, pointer: u32, value: u32) {
278 self.emit(OP_STORE, &[pointer, value]);
279 }
280
281 pub(crate) fn emit_in_bounds_ptr_access_chain(
282 &mut self,
283 result_ty: u32,
284 result: u32,
285 base: u32,
286 element: u32,
287 ) {
288 self.emit(
289 OP_IN_BOUNDS_PTR_ACCESS_CHAIN,
290 &[result_ty, result, base, element],
291 );
292 }
293
294 pub(crate) fn emit_function(&mut self, result_ty: u32, result: u32, control: u32, fn_ty: u32) {
295 self.emit(OP_FUNCTION, &[result_ty, result, control, fn_ty]);
296 }
297
298 pub(crate) fn emit_function_parameter(&mut self, result_ty: u32, result: u32) {
299 self.emit(OP_FUNCTION_PARAMETER, &[result_ty, result]);
300 }
301
302 pub(crate) fn emit_label(&mut self, id: u32) {
303 self.emit(OP_LABEL, &[id]);
304 }
305
306 pub(crate) fn emit_return(&mut self) {
307 self.emit(OP_RETURN, &[]);
308 }
309
310 pub(crate) fn emit_function_end(&mut self) {
311 self.emit(OP_FUNCTION_END, &[]);
312 }
313
314 pub(crate) fn emit_branch(&mut self, target: u32) {
315 self.emit(OP_BRANCH, &[target]);
316 }
317
318 pub(crate) fn emit_branch_conditional(&mut self, cond: u32, true_label: u32, false_label: u32) {
319 self.emit(OP_BRANCH_CONDITIONAL, &[cond, true_label, false_label]);
320 }
321
322 pub(crate) fn emit_selection_merge(&mut self, merge_label: u32) {
323 self.emit(OP_SELECTION_MERGE, &[merge_label, SELECTION_CONTROL_NONE]);
324 }
325
326 pub(crate) fn emit_loop_merge(&mut self, merge_label: u32, continue_label: u32) {
327 self.emit(
328 OP_LOOP_MERGE,
329 &[merge_label, continue_label, LOOP_CONTROL_NONE],
330 );
331 }
332
333 pub(crate) fn emit_opencl_ext(
334 &mut self,
335 ext_id: u32,
336 result_ty: u32,
337 result: u32,
338 inst: u32,
339 args: &[u32],
340 ) {
341 let mut ops = vec![result_ty, result, ext_id, inst];
342 ops.extend_from_slice(args);
343 self.emit(OP_EXT_INST, &ops);
344 }
345}
346
347impl Default for SpvModule {
348 fn default() -> Self {
349 Self::new()
350 }
351}
352
353pub(crate) struct BaseIds {
357 pub(crate) ty_void: u32,
358 pub(crate) ty_bool: u32,
359 pub(crate) ty_uint: u32,
360 pub(crate) ty_float: u32,
361 #[allow(dead_code)]
362 pub(crate) ty_v3uint: u32,
363 #[allow(dead_code)]
364 pub(crate) ty_fn_void: u32,
365 #[allow(dead_code)]
366 pub(crate) ty_ptr_input_v3uint: u32,
367 pub(crate) ty_ptr_cross_float: u32,
368 pub(crate) ty_ptr_func_float: u32,
369 pub(crate) ty_ptr_func_uint: u32,
370 pub(crate) c_uint_0: u32,
371 pub(crate) c_uint_1: u32,
372 pub(crate) c_float_0: u32,
373 pub(crate) c_float_1: u32,
374 pub(crate) var_gid: u32,
375 pub(crate) opencl_ext: u32,
376}
377
378pub(crate) fn emit_preamble(m: &mut SpvModule) -> BaseIds {
384 let ty_void = m.alloc_id();
385 let ty_bool = m.alloc_id();
386 let ty_uint = m.alloc_id();
387 let ty_float = m.alloc_id();
388 let ty_v3uint = m.alloc_id();
389 let ty_fn_void = m.alloc_id();
390 let ty_ptr_input_v3uint = m.alloc_id();
391 let ty_ptr_cross_float = m.alloc_id();
392 let ty_ptr_func_float = m.alloc_id();
393 let ty_ptr_func_uint = m.alloc_id();
394 let c_uint_0 = m.alloc_id();
395 let c_uint_1 = m.alloc_id();
396 let c_float_0 = m.alloc_id();
397 let c_float_1 = m.alloc_id();
398 let var_gid = m.alloc_id();
399 let opencl_ext = m.alloc_id();
400
401 m.emit_capability(CAPABILITY_KERNEL);
403 m.emit_capability(CAPABILITY_ADDRESSES);
404
405 m.emit_ext_inst_import(opencl_ext, "OpenCL.std");
407
408 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
410
411 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
416
417 m.emit_type_void(ty_void);
419 m.emit_type_bool(ty_bool);
420 m.emit_type_int(ty_uint, 32, 0);
421 m.emit_type_float(ty_float, 32);
422 m.emit_type_vector(ty_v3uint, ty_uint, 3);
423 m.emit_type_function(ty_fn_void, ty_void, &[]);
424 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
425 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
426 m.emit_type_pointer(ty_ptr_func_float, STORAGE_CLASS_FUNCTION, ty_float);
427 m.emit_type_pointer(ty_ptr_func_uint, STORAGE_CLASS_FUNCTION, ty_uint);
428
429 m.emit_constant_u32(ty_uint, c_uint_0, 0);
431 m.emit_constant_u32(ty_uint, c_uint_1, 1);
432 m.emit_constant_f32(ty_float, c_float_0, 0.0);
433 m.emit_constant_f32(ty_float, c_float_1, 1.0);
434
435 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
437
438 BaseIds {
439 ty_void,
440 ty_bool,
441 ty_uint,
442 ty_float,
443 ty_v3uint,
444 ty_fn_void,
445 ty_ptr_input_v3uint,
446 ty_ptr_cross_float,
447 ty_ptr_func_float,
448 ty_ptr_func_uint,
449 c_uint_0,
450 c_uint_1,
451 c_float_0,
452 c_float_1,
453 var_gid,
454 opencl_ext,
455 }
456}
457
458pub(crate) fn load_gid_x(m: &mut SpvModule, b: &BaseIds) -> u32 {
460 let gid_val = m.alloc_id();
461 m.emit_load(b.ty_v3uint, gid_val, b.var_gid);
462 let gid_x = m.alloc_id();
463 m.emit(OP_COMPOSITE_EXTRACT, &[b.ty_uint, gid_x, gid_val, 0]);
464 gid_x
465}
466
467pub fn unary_compute_shader(op: UnaryOp) -> Vec<u32> {
473 let mut m = SpvModule::new();
474 let b = emit_preamble(&mut m);
475
476 let main_fn = m.alloc_id();
477 let fn_ty = m.alloc_id();
478 let p_input = m.alloc_id();
479 let p_output = m.alloc_id();
480 let p_count = m.alloc_id();
481
482 m.emit_type_function(
484 fn_ty,
485 b.ty_void,
486 &[b.ty_ptr_cross_float, b.ty_ptr_cross_float, b.ty_uint],
487 );
488
489 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
491 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
492
493 let label_entry = m.alloc_id();
495 let label_body = m.alloc_id();
496 let label_merge = m.alloc_id();
497
498 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
500 m.emit_function_parameter(b.ty_ptr_cross_float, p_input);
501 m.emit_function_parameter(b.ty_ptr_cross_float, p_output);
502 m.emit_function_parameter(b.ty_uint, p_count);
503 m.emit_label(label_entry);
504
505 let gid = load_gid_x(&mut m, &b);
506
507 let cond = m.alloc_id();
509 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, p_count]);
510 m.emit_selection_merge(label_merge);
511 m.emit_branch_conditional(cond, label_body, label_merge);
512
513 m.emit_label(label_body);
514
515 let inp_ptr = m.alloc_id();
517 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, inp_ptr, p_input, gid);
518 let inp_val = m.alloc_id();
519 m.emit_load(b.ty_float, inp_val, inp_ptr);
520
521 let result = emit_unary_op(&mut m, &b, op, inp_val);
522
523 let out_ptr = m.alloc_id();
525 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_output, gid);
526 m.emit_store(out_ptr, result);
527
528 m.emit_branch(label_merge);
529
530 m.emit_label(label_merge);
531 m.emit_return();
532 m.emit_function_end();
533
534 m.finalize()
535}
536
537fn emit_unary_op(m: &mut SpvModule, b: &BaseIds, op: UnaryOp, x: u32) -> u32 {
539 let result = m.alloc_id();
540 match op {
541 UnaryOp::Relu => {
542 m.emit_opencl_ext(
543 b.opencl_ext,
544 b.ty_float,
545 result,
546 OPENCL_FMAX,
547 &[b.c_float_0, x],
548 );
549 }
550 UnaryOp::Sigmoid => {
551 let neg_x = m.alloc_id();
552 m.emit(OP_F_NEGATE, &[b.ty_float, neg_x, x]);
553 let exp_neg_x = m.alloc_id();
554 m.emit_opencl_ext(b.opencl_ext, b.ty_float, exp_neg_x, OPENCL_EXP, &[neg_x]);
555 let one_plus = m.alloc_id();
556 m.emit(OP_F_ADD, &[b.ty_float, one_plus, b.c_float_1, exp_neg_x]);
557 m.emit(OP_F_DIV, &[b.ty_float, result, b.c_float_1, one_plus]);
558 }
559 UnaryOp::Tanh => {
560 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_TANH, &[x]);
561 }
562 UnaryOp::Exp => {
563 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_EXP, &[x]);
564 }
565 UnaryOp::Log => {
566 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_LOG, &[x]);
567 }
568 UnaryOp::Sqrt => {
569 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_SQRT, &[x]);
570 }
571 UnaryOp::Abs => {
572 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_FABS, &[x]);
573 }
574 UnaryOp::Neg => {
575 m.emit(OP_F_NEGATE, &[b.ty_float, result, x]);
576 }
577 }
578 result
579}
580
581pub fn binary_compute_shader(op: BinaryOp) -> Vec<u32> {
588 let mut m = SpvModule::new();
589 let b = emit_preamble(&mut m);
590
591 let main_fn = m.alloc_id();
592 let fn_ty = m.alloc_id();
593 let p_a = m.alloc_id();
594 let p_b = m.alloc_id();
595 let p_out = m.alloc_id();
596 let p_count = m.alloc_id();
597
598 m.emit_type_function(
600 fn_ty,
601 b.ty_void,
602 &[
603 b.ty_ptr_cross_float,
604 b.ty_ptr_cross_float,
605 b.ty_ptr_cross_float,
606 b.ty_uint,
607 ],
608 );
609
610 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
611 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
612
613 let label_entry = m.alloc_id();
614 let label_body = m.alloc_id();
615 let label_merge = m.alloc_id();
616
617 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
618 m.emit_function_parameter(b.ty_ptr_cross_float, p_a);
619 m.emit_function_parameter(b.ty_ptr_cross_float, p_b);
620 m.emit_function_parameter(b.ty_ptr_cross_float, p_out);
621 m.emit_function_parameter(b.ty_uint, p_count);
622 m.emit_label(label_entry);
623
624 let gid = load_gid_x(&mut m, &b);
625
626 let cond = m.alloc_id();
627 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, p_count]);
628 m.emit_selection_merge(label_merge);
629 m.emit_branch_conditional(cond, label_body, label_merge);
630
631 m.emit_label(label_body);
632
633 let a_ptr = m.alloc_id();
634 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, a_ptr, p_a, gid);
635 let a_val = m.alloc_id();
636 m.emit_load(b.ty_float, a_val, a_ptr);
637
638 let b_ptr = m.alloc_id();
639 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, b_ptr, p_b, gid);
640 let b_val = m.alloc_id();
641 m.emit_load(b.ty_float, b_val, b_ptr);
642
643 let result = emit_binary_op(&mut m, &b, op, a_val, b_val);
644
645 let out_ptr = m.alloc_id();
646 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_out, gid);
647 m.emit_store(out_ptr, result);
648
649 m.emit_branch(label_merge);
650
651 m.emit_label(label_merge);
652 m.emit_return();
653 m.emit_function_end();
654
655 m.finalize()
656}
657
658fn emit_binary_op(m: &mut SpvModule, b: &BaseIds, op: BinaryOp, lhs: u32, rhs: u32) -> u32 {
659 let result = m.alloc_id();
660 match op {
661 BinaryOp::Add => m.emit(OP_F_ADD, &[b.ty_float, result, lhs, rhs]),
662 BinaryOp::Sub => m.emit(OP_F_SUB, &[b.ty_float, result, lhs, rhs]),
663 BinaryOp::Mul => m.emit(OP_F_MUL, &[b.ty_float, result, lhs, rhs]),
664 BinaryOp::Div => m.emit(OP_F_DIV, &[b.ty_float, result, lhs, rhs]),
665 BinaryOp::Max => {
666 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_FMAX, &[lhs, rhs]);
667 }
668 BinaryOp::Min => {
669 m.emit_opencl_ext(b.opencl_ext, b.ty_float, result, OPENCL_FMIN, &[lhs, rhs]);
670 }
671 }
672 result
673}
674
675pub fn reduce_compute_shader(op: ReduceOp) -> Vec<u32> {
684 let mut m = SpvModule::new();
685 let b = emit_preamble(&mut m);
686
687 let main_fn = m.alloc_id();
688 let fn_ty = m.alloc_id();
689 let p_input = m.alloc_id();
690 let p_output = m.alloc_id();
691 let p_outer = m.alloc_id();
692 let p_reduce = m.alloc_id();
693 let p_inner = m.alloc_id();
694
695 m.emit_type_function(
697 fn_ty,
698 b.ty_void,
699 &[
700 b.ty_ptr_cross_float,
701 b.ty_ptr_cross_float,
702 b.ty_uint,
703 b.ty_uint,
704 b.ty_uint,
705 ],
706 );
707
708 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
709 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
710
711 let label_entry = m.alloc_id();
712 let label_bounds_body = m.alloc_id();
713 let label_bounds_merge = m.alloc_id();
714 let label_loop_header = m.alloc_id();
715 let label_loop_body = m.alloc_id();
716 let label_loop_continue = m.alloc_id();
717 let label_loop_merge = m.alloc_id();
718
719 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
720 m.emit_function_parameter(b.ty_ptr_cross_float, p_input);
721 m.emit_function_parameter(b.ty_ptr_cross_float, p_output);
722 m.emit_function_parameter(b.ty_uint, p_outer);
723 m.emit_function_parameter(b.ty_uint, p_reduce);
724 m.emit_function_parameter(b.ty_uint, p_inner);
725 m.emit_label(label_entry);
726
727 let gid = load_gid_x(&mut m, &b);
728
729 let total_output = m.alloc_id();
731 m.emit(OP_I_MUL, &[b.ty_uint, total_output, p_outer, p_inner]);
732
733 let cond_bounds = m.alloc_id();
735 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond_bounds, gid, total_output]);
736 m.emit_selection_merge(label_bounds_merge);
737 m.emit_branch_conditional(cond_bounds, label_bounds_body, label_bounds_merge);
738
739 m.emit_label(label_bounds_body);
740
741 let outer_idx = m.alloc_id();
743 m.emit(OP_U_DIV, &[b.ty_uint, outer_idx, gid, p_inner]);
744 let inner_idx = m.alloc_id();
745 m.emit(OP_U_MOD, &[b.ty_uint, inner_idx, gid, p_inner]);
746
747 let t1 = m.alloc_id();
749 m.emit(OP_I_MUL, &[b.ty_uint, t1, outer_idx, p_reduce]);
750 let t2 = m.alloc_id();
751 m.emit(OP_I_MUL, &[b.ty_uint, t2, t1, p_inner]);
752 let base_idx = m.alloc_id();
753 m.emit(OP_I_ADD, &[b.ty_uint, base_idx, t2, inner_idx]);
754
755 let var_i = m.alloc_id();
757 m.emit_variable(b.ty_ptr_func_uint, var_i, STORAGE_CLASS_FUNCTION);
758 m.emit_store(var_i, b.c_uint_0);
759
760 let var_acc = m.alloc_id();
762 m.emit_variable(b.ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
763 let init_val = match op {
764 ReduceOp::Sum | ReduceOp::Mean => b.c_float_0,
765 ReduceOp::Max => {
766 let neg_inf = m.alloc_id();
767 m.emit_constant_f32(b.ty_float, neg_inf, f32::NEG_INFINITY);
768 neg_inf
769 }
770 ReduceOp::Min => {
771 let pos_inf = m.alloc_id();
772 m.emit_constant_f32(b.ty_float, pos_inf, f32::INFINITY);
773 pos_inf
774 }
775 };
776 m.emit_store(var_acc, init_val);
777
778 m.emit_branch(label_loop_header);
779
780 m.emit_label(label_loop_header);
782 let i_val = m.alloc_id();
783 m.emit_load(b.ty_uint, i_val, var_i);
784 let loop_cond = m.alloc_id();
785 m.emit(OP_U_LESS_THAN, &[b.ty_bool, loop_cond, i_val, p_reduce]);
786 m.emit_loop_merge(label_loop_merge, label_loop_continue);
787 m.emit_branch_conditional(loop_cond, label_loop_body, label_loop_merge);
788
789 m.emit_label(label_loop_body);
791
792 let i_times_inner = m.alloc_id();
794 m.emit(OP_I_MUL, &[b.ty_uint, i_times_inner, i_val, p_inner]);
795 let input_idx = m.alloc_id();
796 m.emit(OP_I_ADD, &[b.ty_uint, input_idx, base_idx, i_times_inner]);
797
798 let inp_ptr = m.alloc_id();
799 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, inp_ptr, p_input, input_idx);
800 let inp_val = m.alloc_id();
801 m.emit_load(b.ty_float, inp_val, inp_ptr);
802
803 let acc_val = m.alloc_id();
804 m.emit_load(b.ty_float, acc_val, var_acc);
805
806 let new_acc = m.alloc_id();
807 match op {
808 ReduceOp::Sum | ReduceOp::Mean => {
809 m.emit(OP_F_ADD, &[b.ty_float, new_acc, acc_val, inp_val]);
810 }
811 ReduceOp::Max => {
812 m.emit_opencl_ext(
813 b.opencl_ext,
814 b.ty_float,
815 new_acc,
816 OPENCL_FMAX,
817 &[acc_val, inp_val],
818 );
819 }
820 ReduceOp::Min => {
821 m.emit_opencl_ext(
822 b.opencl_ext,
823 b.ty_float,
824 new_acc,
825 OPENCL_FMIN,
826 &[acc_val, inp_val],
827 );
828 }
829 }
830 m.emit_store(var_acc, new_acc);
831
832 m.emit_branch(label_loop_continue);
833
834 m.emit_label(label_loop_continue);
836 let i_inc = m.alloc_id();
837 m.emit(OP_I_ADD, &[b.ty_uint, i_inc, i_val, b.c_uint_1]);
838 m.emit_store(var_i, i_inc);
839 m.emit_branch(label_loop_header);
840
841 m.emit_label(label_loop_merge);
843
844 let final_acc = m.alloc_id();
845 m.emit_load(b.ty_float, final_acc, var_acc);
846
847 let store_val = if op == ReduceOp::Mean {
848 let reduce_f = m.alloc_id();
849 m.emit(OP_CONVERT_U_TO_F, &[b.ty_float, reduce_f, p_reduce]);
850 let mean_val = m.alloc_id();
851 m.emit(OP_F_DIV, &[b.ty_float, mean_val, final_acc, reduce_f]);
852 mean_val
853 } else {
854 final_acc
855 };
856
857 let out_ptr = m.alloc_id();
858 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_output, gid);
859 m.emit_store(out_ptr, store_val);
860
861 m.emit_branch(label_bounds_merge);
862
863 m.emit_label(label_bounds_merge);
864 m.emit_return();
865 m.emit_function_end();
866
867 m.finalize()
868}
869
870pub fn gemm_compute_shader() -> Vec<u32> {
880 let mut m = SpvModule::new();
881 let b = emit_preamble(&mut m);
882
883 let main_fn = m.alloc_id();
884 let fn_ty = m.alloc_id();
885 let p_a = m.alloc_id();
886 let p_b = m.alloc_id();
887 let p_c = m.alloc_id();
888 let p_m = m.alloc_id();
889 let p_n = m.alloc_id();
890 let p_k = m.alloc_id();
891 let p_alpha = m.alloc_id();
892 let p_beta = m.alloc_id();
893
894 m.emit_type_function(
896 fn_ty,
897 b.ty_void,
898 &[
899 b.ty_ptr_cross_float,
900 b.ty_ptr_cross_float,
901 b.ty_ptr_cross_float,
902 b.ty_uint,
903 b.ty_uint,
904 b.ty_uint,
905 b.ty_float,
906 b.ty_float,
907 ],
908 );
909
910 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
911 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
912
913 let label_entry = m.alloc_id();
914 let label_bounds_body = m.alloc_id();
915 let label_bounds_merge = m.alloc_id();
916 let label_loop_header = m.alloc_id();
917 let label_loop_body = m.alloc_id();
918 let label_loop_continue = m.alloc_id();
919 let label_loop_merge = m.alloc_id();
920
921 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
922 m.emit_function_parameter(b.ty_ptr_cross_float, p_a);
923 m.emit_function_parameter(b.ty_ptr_cross_float, p_b);
924 m.emit_function_parameter(b.ty_ptr_cross_float, p_c);
925 m.emit_function_parameter(b.ty_uint, p_m);
926 m.emit_function_parameter(b.ty_uint, p_n);
927 m.emit_function_parameter(b.ty_uint, p_k);
928 m.emit_function_parameter(b.ty_float, p_alpha);
929 m.emit_function_parameter(b.ty_float, p_beta);
930 m.emit_label(label_entry);
931
932 let gid = load_gid_x(&mut m, &b);
933
934 let total = m.alloc_id();
936 m.emit(OP_I_MUL, &[b.ty_uint, total, p_m, p_n]);
937
938 let cond = m.alloc_id();
940 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, total]);
941 m.emit_selection_merge(label_bounds_merge);
942 m.emit_branch_conditional(cond, label_bounds_body, label_bounds_merge);
943
944 m.emit_label(label_bounds_body);
945
946 let row = m.alloc_id();
948 m.emit(OP_U_DIV, &[b.ty_uint, row, gid, p_n]);
949 let col = m.alloc_id();
950 m.emit(OP_U_MOD, &[b.ty_uint, col, gid, p_n]);
951
952 let var_i = m.alloc_id();
954 m.emit_variable(b.ty_ptr_func_uint, var_i, STORAGE_CLASS_FUNCTION);
955 m.emit_store(var_i, b.c_uint_0);
956 let var_acc = m.alloc_id();
957 m.emit_variable(b.ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
958 m.emit_store(var_acc, b.c_float_0);
959
960 m.emit_branch(label_loop_header);
961
962 m.emit_label(label_loop_header);
964 let i_val = m.alloc_id();
965 m.emit_load(b.ty_uint, i_val, var_i);
966 let loop_cond = m.alloc_id();
967 m.emit(OP_U_LESS_THAN, &[b.ty_bool, loop_cond, i_val, p_k]);
968 m.emit_loop_merge(label_loop_merge, label_loop_continue);
969 m.emit_branch_conditional(loop_cond, label_loop_body, label_loop_merge);
970
971 m.emit_label(label_loop_body);
973
974 let row_k = m.alloc_id();
976 m.emit(OP_I_MUL, &[b.ty_uint, row_k, row, p_k]);
977 let a_idx = m.alloc_id();
978 m.emit(OP_I_ADD, &[b.ty_uint, a_idx, row_k, i_val]);
979
980 let i_n = m.alloc_id();
982 m.emit(OP_I_MUL, &[b.ty_uint, i_n, i_val, p_n]);
983 let b_idx = m.alloc_id();
984 m.emit(OP_I_ADD, &[b.ty_uint, b_idx, i_n, col]);
985
986 let a_ptr = m.alloc_id();
987 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, a_ptr, p_a, a_idx);
988 let a_val = m.alloc_id();
989 m.emit_load(b.ty_float, a_val, a_ptr);
990
991 let b_ptr = m.alloc_id();
992 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, b_ptr, p_b, b_idx);
993 let b_val = m.alloc_id();
994 m.emit_load(b.ty_float, b_val, b_ptr);
995
996 let prod = m.alloc_id();
997 m.emit(OP_F_MUL, &[b.ty_float, prod, a_val, b_val]);
998 let old_acc = m.alloc_id();
999 m.emit_load(b.ty_float, old_acc, var_acc);
1000 let new_acc = m.alloc_id();
1001 m.emit(OP_F_ADD, &[b.ty_float, new_acc, old_acc, prod]);
1002 m.emit_store(var_acc, new_acc);
1003
1004 m.emit_branch(label_loop_continue);
1005
1006 m.emit_label(label_loop_continue);
1008 let i_inc = m.alloc_id();
1009 m.emit(OP_I_ADD, &[b.ty_uint, i_inc, i_val, b.c_uint_1]);
1010 m.emit_store(var_i, i_inc);
1011 m.emit_branch(label_loop_header);
1012
1013 m.emit_label(label_loop_merge);
1015
1016 let final_acc = m.alloc_id();
1018 m.emit_load(b.ty_float, final_acc, var_acc);
1019 let alpha_acc = m.alloc_id();
1020 m.emit(OP_F_MUL, &[b.ty_float, alpha_acc, p_alpha, final_acc]);
1021
1022 let c_ptr = m.alloc_id();
1023 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, c_ptr, p_c, gid);
1024 let c_old = m.alloc_id();
1025 m.emit_load(b.ty_float, c_old, c_ptr);
1026 let beta_c = m.alloc_id();
1027 m.emit(OP_F_MUL, &[b.ty_float, beta_c, p_beta, c_old]);
1028 let c_new = m.alloc_id();
1029 m.emit(OP_F_ADD, &[b.ty_float, c_new, alpha_acc, beta_c]);
1030 m.emit_store(c_ptr, c_new);
1031
1032 m.emit_branch(label_bounds_merge);
1033
1034 m.emit_label(label_bounds_merge);
1035 m.emit_return();
1036 m.emit_function_end();
1037
1038 m.finalize()
1039}
1040
1041fn load_gid_z(m: &mut SpvModule, b: &BaseIds) -> u32 {
1045 let gid_val = m.alloc_id();
1046 m.emit_load(b.ty_v3uint, gid_val, b.var_gid);
1047 let gid_z = m.alloc_id();
1048 m.emit(OP_COMPOSITE_EXTRACT, &[b.ty_uint, gid_z, gid_val, 2]);
1049 gid_z
1050}
1051
1052pub fn batched_gemm_compute_shader() -> Vec<u32> {
1067 let mut m = SpvModule::new();
1068 let b = emit_preamble(&mut m);
1069
1070 let main_fn = m.alloc_id();
1071 let fn_ty = m.alloc_id();
1072 let p_a = m.alloc_id();
1073 let p_b = m.alloc_id();
1074 let p_c = m.alloc_id();
1075 let p_m = m.alloc_id();
1076 let p_n = m.alloc_id();
1077 let p_k = m.alloc_id();
1078 let p_alpha = m.alloc_id();
1079 let p_beta = m.alloc_id();
1080 let p_batch_count = m.alloc_id();
1081 let p_stride_a = m.alloc_id();
1082 let p_stride_b = m.alloc_id();
1083 let p_stride_c = m.alloc_id();
1084
1085 m.emit_type_function(
1088 fn_ty,
1089 b.ty_void,
1090 &[
1091 b.ty_ptr_cross_float,
1092 b.ty_ptr_cross_float,
1093 b.ty_ptr_cross_float,
1094 b.ty_uint,
1095 b.ty_uint,
1096 b.ty_uint,
1097 b.ty_float,
1098 b.ty_float,
1099 b.ty_uint,
1100 b.ty_uint,
1101 b.ty_uint,
1102 b.ty_uint,
1103 ],
1104 );
1105
1106 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
1107 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
1108
1109 let label_entry = m.alloc_id();
1110 let label_bounds_body = m.alloc_id();
1111 let label_bounds_merge = m.alloc_id();
1112 let label_loop_header = m.alloc_id();
1113 let label_loop_body = m.alloc_id();
1114 let label_loop_continue = m.alloc_id();
1115 let label_loop_merge = m.alloc_id();
1116
1117 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
1118 m.emit_function_parameter(b.ty_ptr_cross_float, p_a);
1119 m.emit_function_parameter(b.ty_ptr_cross_float, p_b);
1120 m.emit_function_parameter(b.ty_ptr_cross_float, p_c);
1121 m.emit_function_parameter(b.ty_uint, p_m);
1122 m.emit_function_parameter(b.ty_uint, p_n);
1123 m.emit_function_parameter(b.ty_uint, p_k);
1124 m.emit_function_parameter(b.ty_float, p_alpha);
1125 m.emit_function_parameter(b.ty_float, p_beta);
1126 m.emit_function_parameter(b.ty_uint, p_batch_count);
1127 m.emit_function_parameter(b.ty_uint, p_stride_a);
1128 m.emit_function_parameter(b.ty_uint, p_stride_b);
1129 m.emit_function_parameter(b.ty_uint, p_stride_c);
1130 m.emit_label(label_entry);
1131
1132 let gid = load_gid_x(&mut m, &b);
1134 let batch_idx = load_gid_z(&mut m, &b);
1136
1137 let total = m.alloc_id();
1139 m.emit(OP_I_MUL, &[b.ty_uint, total, p_m, p_n]);
1140
1141 let cond1 = m.alloc_id();
1143 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond1, gid, total]);
1144 let cond2 = m.alloc_id();
1145 m.emit(
1146 OP_U_LESS_THAN,
1147 &[b.ty_bool, cond2, batch_idx, p_batch_count],
1148 );
1149 let cond = m.alloc_id();
1151 m.emit(166, &[b.ty_bool, cond, cond1, cond2]);
1153 m.emit_selection_merge(label_bounds_merge);
1154 m.emit_branch_conditional(cond, label_bounds_body, label_bounds_merge);
1155
1156 m.emit_label(label_bounds_body);
1157
1158 let a_offset = m.alloc_id();
1160 m.emit(OP_I_MUL, &[b.ty_uint, a_offset, batch_idx, p_stride_a]);
1161 let b_offset = m.alloc_id();
1162 m.emit(OP_I_MUL, &[b.ty_uint, b_offset, batch_idx, p_stride_b]);
1163 let c_offset = m.alloc_id();
1164 m.emit(OP_I_MUL, &[b.ty_uint, c_offset, batch_idx, p_stride_c]);
1165
1166 let a_batch = m.alloc_id();
1168 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, a_batch, p_a, a_offset);
1169 let b_batch = m.alloc_id();
1170 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, b_batch, p_b, b_offset);
1171 let c_batch = m.alloc_id();
1172 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, c_batch, p_c, c_offset);
1173
1174 let row = m.alloc_id();
1176 m.emit(OP_U_DIV, &[b.ty_uint, row, gid, p_n]);
1177 let col = m.alloc_id();
1178 m.emit(OP_U_MOD, &[b.ty_uint, col, gid, p_n]);
1179
1180 let var_i = m.alloc_id();
1182 m.emit_variable(b.ty_ptr_func_uint, var_i, STORAGE_CLASS_FUNCTION);
1183 m.emit_store(var_i, b.c_uint_0);
1184 let var_acc = m.alloc_id();
1185 m.emit_variable(b.ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
1186 m.emit_store(var_acc, b.c_float_0);
1187
1188 m.emit_branch(label_loop_header);
1189
1190 m.emit_label(label_loop_header);
1192 let i_val = m.alloc_id();
1193 m.emit_load(b.ty_uint, i_val, var_i);
1194 let loop_cond = m.alloc_id();
1195 m.emit(OP_U_LESS_THAN, &[b.ty_bool, loop_cond, i_val, p_k]);
1196 m.emit_loop_merge(label_loop_merge, label_loop_continue);
1197 m.emit_branch_conditional(loop_cond, label_loop_body, label_loop_merge);
1198
1199 m.emit_label(label_loop_body);
1201
1202 let row_k = m.alloc_id();
1204 m.emit(OP_I_MUL, &[b.ty_uint, row_k, row, p_k]);
1205 let a_idx = m.alloc_id();
1206 m.emit(OP_I_ADD, &[b.ty_uint, a_idx, row_k, i_val]);
1207
1208 let i_n = m.alloc_id();
1210 m.emit(OP_I_MUL, &[b.ty_uint, i_n, i_val, p_n]);
1211 let b_idx = m.alloc_id();
1212 m.emit(OP_I_ADD, &[b.ty_uint, b_idx, i_n, col]);
1213
1214 let a_ptr = m.alloc_id();
1215 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, a_ptr, a_batch, a_idx);
1216 let a_val = m.alloc_id();
1217 m.emit_load(b.ty_float, a_val, a_ptr);
1218
1219 let b_ptr = m.alloc_id();
1220 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, b_ptr, b_batch, b_idx);
1221 let b_val = m.alloc_id();
1222 m.emit_load(b.ty_float, b_val, b_ptr);
1223
1224 let prod = m.alloc_id();
1225 m.emit(OP_F_MUL, &[b.ty_float, prod, a_val, b_val]);
1226 let old_acc = m.alloc_id();
1227 m.emit_load(b.ty_float, old_acc, var_acc);
1228 let new_acc = m.alloc_id();
1229 m.emit(OP_F_ADD, &[b.ty_float, new_acc, old_acc, prod]);
1230 m.emit_store(var_acc, new_acc);
1231
1232 m.emit_branch(label_loop_continue);
1233
1234 m.emit_label(label_loop_continue);
1236 let i_inc = m.alloc_id();
1237 m.emit(OP_I_ADD, &[b.ty_uint, i_inc, i_val, b.c_uint_1]);
1238 m.emit_store(var_i, i_inc);
1239 m.emit_branch(label_loop_header);
1240
1241 m.emit_label(label_loop_merge);
1243
1244 let final_acc = m.alloc_id();
1246 m.emit_load(b.ty_float, final_acc, var_acc);
1247 let alpha_acc = m.alloc_id();
1248 m.emit(OP_F_MUL, &[b.ty_float, alpha_acc, p_alpha, final_acc]);
1249
1250 let c_ptr = m.alloc_id();
1251 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, c_ptr, c_batch, gid);
1252 let c_old = m.alloc_id();
1253 m.emit_load(b.ty_float, c_old, c_ptr);
1254 let beta_c = m.alloc_id();
1255 m.emit(OP_F_MUL, &[b.ty_float, beta_c, p_beta, c_old]);
1256 let c_new = m.alloc_id();
1257 m.emit(OP_F_ADD, &[b.ty_float, c_new, alpha_acc, beta_c]);
1258 m.emit_store(c_ptr, c_new);
1259
1260 m.emit_branch(label_bounds_merge);
1261
1262 m.emit_label(label_bounds_merge);
1263 m.emit_return();
1264 m.emit_function_end();
1265
1266 m.finalize()
1267}
1268
1269pub fn trivial_compute_shader() -> Vec<u32> {
1275 let mut m = SpvModule::new();
1276
1277 let id_main_fn = m.alloc_id();
1278 let id_void = m.alloc_id();
1279 let id_void_fn = m.alloc_id();
1280 let id_label = m.alloc_id();
1281
1282 m.emit_capability(CAPABILITY_SHADER);
1283 m.emit_memory_model(ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450);
1284
1285 let mut entry_words = vec![EXECUTION_MODEL_GLCOMPUTE, id_main_fn];
1286 entry_words.extend(SpvModule::string_words("main"));
1287 m.emit(OP_ENTRY_POINT, &entry_words);
1288
1289 m.emit_execution_mode_local_size(id_main_fn, 1, 1, 1);
1290
1291 m.emit_type_void(id_void);
1292 m.emit_type_function(id_void_fn, id_void, &[]);
1293
1294 m.emit_function(id_void, id_main_fn, FUNCTION_CONTROL_NONE, id_void_fn);
1295 m.emit_label(id_label);
1296 m.emit_return();
1297 m.emit_function_end();
1298
1299 m.finalize()
1300}
1301
1302pub fn trivial_compute_shader_bytes() -> Vec<u8> {
1305 trivial_compute_shader()
1306 .iter()
1307 .flat_map(|w| w.to_ne_bytes())
1308 .collect()
1309}
1310
1311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1320pub enum ExtMathFn {
1321 Erf,
1323 Erfc,
1325 Sin,
1327 Cos,
1329 Atan,
1331 Tanh,
1333 Cbrt,
1335 Rsqrt,
1337}
1338
1339impl ExtMathFn {
1340 #[must_use]
1342 pub fn opencl_inst(self) -> u32 {
1343 match self {
1344 ExtMathFn::Erf => OPENCL_ERF,
1345 ExtMathFn::Erfc => OPENCL_ERFC,
1346 ExtMathFn::Sin => OPENCL_SIN,
1347 ExtMathFn::Cos => OPENCL_COS,
1348 ExtMathFn::Atan => OPENCL_ATAN,
1349 ExtMathFn::Tanh => OPENCL_TANH,
1350 ExtMathFn::Cbrt => OPENCL_CBRT,
1351 ExtMathFn::Rsqrt => OPENCL_RSQRT,
1352 }
1353 }
1354
1355 #[must_use]
1357 pub fn name(self) -> &'static str {
1358 match self {
1359 ExtMathFn::Erf => "erf",
1360 ExtMathFn::Erfc => "erfc",
1361 ExtMathFn::Sin => "sin",
1362 ExtMathFn::Cos => "cos",
1363 ExtMathFn::Atan => "atan",
1364 ExtMathFn::Tanh => "tanh",
1365 ExtMathFn::Cbrt => "cbrt",
1366 ExtMathFn::Rsqrt => "rsqrt",
1367 }
1368 }
1369}
1370
1371pub fn ext_math_compute_shader(func: ExtMathFn) -> Vec<u32> {
1377 let mut m = SpvModule::new();
1378 let b = emit_preamble(&mut m);
1379
1380 let main_fn = m.alloc_id();
1381 let fn_ty = m.alloc_id();
1382 let p_input = m.alloc_id();
1383 let p_output = m.alloc_id();
1384 let p_count = m.alloc_id();
1385
1386 m.emit_type_function(
1387 fn_ty,
1388 b.ty_void,
1389 &[b.ty_ptr_cross_float, b.ty_ptr_cross_float, b.ty_uint],
1390 );
1391
1392 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "main", &[b.var_gid]);
1393 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
1394
1395 let label_entry = m.alloc_id();
1396 let label_body = m.alloc_id();
1397 let label_merge = m.alloc_id();
1398
1399 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
1400 m.emit_function_parameter(b.ty_ptr_cross_float, p_input);
1401 m.emit_function_parameter(b.ty_ptr_cross_float, p_output);
1402 m.emit_function_parameter(b.ty_uint, p_count);
1403 m.emit_label(label_entry);
1404
1405 let gid = load_gid_x(&mut m, &b);
1406 let cond = m.alloc_id();
1407 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, p_count]);
1408 m.emit_selection_merge(label_merge);
1409 m.emit_branch_conditional(cond, label_body, label_merge);
1410
1411 m.emit_label(label_body);
1412
1413 let inp_ptr = m.alloc_id();
1414 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, inp_ptr, p_input, gid);
1415 let inp_val = m.alloc_id();
1416 m.emit_load(b.ty_float, inp_val, inp_ptr);
1417
1418 let result = m.alloc_id();
1419 m.emit_opencl_ext(
1420 b.opencl_ext,
1421 b.ty_float,
1422 result,
1423 func.opencl_inst(),
1424 &[inp_val],
1425 );
1426
1427 let out_ptr = m.alloc_id();
1428 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_output, gid);
1429 m.emit_store(out_ptr, result);
1430
1431 m.emit_branch(label_merge);
1432 m.emit_label(label_merge);
1433 m.emit_return();
1434 m.emit_function_end();
1435
1436 m.finalize()
1437}
1438
1439pub fn atan2_compute_shader() -> Vec<u32> {
1448 let mut m = SpvModule::new();
1449 let b = emit_preamble(&mut m);
1450
1451 let main_fn = m.alloc_id();
1452 let fn_ty = m.alloc_id();
1453 let p_y = m.alloc_id();
1454 let p_x = m.alloc_id();
1455 let p_out = m.alloc_id();
1456 let p_count = m.alloc_id();
1457
1458 m.emit_type_function(
1459 fn_ty,
1460 b.ty_void,
1461 &[
1462 b.ty_ptr_cross_float,
1463 b.ty_ptr_cross_float,
1464 b.ty_ptr_cross_float,
1465 b.ty_uint,
1466 ],
1467 );
1468
1469 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "atan2", &[b.var_gid]);
1470 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
1471
1472 let label_entry = m.alloc_id();
1473 let label_body = m.alloc_id();
1474 let label_merge = m.alloc_id();
1475
1476 m.emit_function(b.ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
1477 m.emit_function_parameter(b.ty_ptr_cross_float, p_y);
1478 m.emit_function_parameter(b.ty_ptr_cross_float, p_x);
1479 m.emit_function_parameter(b.ty_ptr_cross_float, p_out);
1480 m.emit_function_parameter(b.ty_uint, p_count);
1481 m.emit_label(label_entry);
1482
1483 let gid = load_gid_x(&mut m, &b);
1484 let cond = m.alloc_id();
1485 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, p_count]);
1486 m.emit_selection_merge(label_merge);
1487 m.emit_branch_conditional(cond, label_body, label_merge);
1488
1489 m.emit_label(label_body);
1490
1491 let y_ptr = m.alloc_id();
1492 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, y_ptr, p_y, gid);
1493 let y_val = m.alloc_id();
1494 m.emit_load(b.ty_float, y_val, y_ptr);
1495
1496 let x_ptr = m.alloc_id();
1497 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, x_ptr, p_x, gid);
1498 let x_val = m.alloc_id();
1499 m.emit_load(b.ty_float, x_val, x_ptr);
1500
1501 let result = m.alloc_id();
1502 m.emit_opencl_ext(
1503 b.opencl_ext,
1504 b.ty_float,
1505 result,
1506 OPENCL_ATAN2,
1507 &[y_val, x_val],
1508 );
1509
1510 let out_ptr = m.alloc_id();
1511 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_out, gid);
1512 m.emit_store(out_ptr, result);
1513
1514 m.emit_branch(label_merge);
1515 m.emit_label(label_merge);
1516 m.emit_return();
1517 m.emit_function_end();
1518
1519 m.finalize()
1520}
1521
1522#[cfg(test)]
1525mod tests {
1526 use super::*;
1527
1528 fn check_valid_spirv(words: &[u32]) {
1529 assert!(words.len() >= 5, "too short for SPIR-V header");
1530 assert_eq!(words[0], SPIRV_MAGIC, "bad magic");
1531 assert!(words[3] > 0, "ID bound must be > 0");
1532 assert_eq!(words[4], 0, "schema must be 0");
1533 }
1534
1535 #[test]
1536 fn placeholder_spv_valid_magic() {
1537 let words = trivial_compute_shader();
1538 check_valid_spirv(&words);
1539 }
1540
1541 #[test]
1542 fn placeholder_spv_word_aligned() {
1543 let bytes = trivial_compute_shader_bytes();
1544 assert_eq!(bytes.len() % 4, 0);
1545 }
1546
1547 #[test]
1548 fn placeholder_spv_version_and_schema() {
1549 let words = trivial_compute_shader();
1550 assert!(words[1] >= 0x0001_0000);
1551 assert_eq!(words[4], 0);
1552 }
1553
1554 #[test]
1555 fn placeholder_spv_nonzero_bound() {
1556 let words = trivial_compute_shader();
1557 assert!(words[3] > 0);
1558 }
1559
1560 #[test]
1561 fn spv_module_id_allocation_is_monotonic() {
1562 let mut m = SpvModule::new();
1563 let id1 = m.alloc_id();
1564 let id2 = m.alloc_id();
1565 assert!(id2 > id1);
1566 }
1567
1568 #[test]
1569 fn string_words_null_terminated() {
1570 let words = SpvModule::string_words("abc");
1571 assert!(!words.is_empty());
1572 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
1573 assert_eq!(bytes[0], b'a');
1574 assert_eq!(bytes[1], b'b');
1575 assert_eq!(bytes[2], b'c');
1576 assert_eq!(bytes[3], 0);
1577 }
1578
1579 #[test]
1580 fn string_words_empty_string() {
1581 let words = SpvModule::string_words("");
1582 assert!(!words.is_empty());
1583 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
1584 assert_eq!(bytes[0], 0);
1585 }
1586
1587 #[test]
1588 fn generator_magic_is_level_zero() {
1589 assert_eq!(SPIRV_GENERATOR, 0x000D_0002);
1590 assert_ne!(SPIRV_GENERATOR, 0x000D_0001);
1591 }
1592
1593 #[test]
1596 fn unary_shader_all_ops() {
1597 let ops = [
1598 UnaryOp::Relu,
1599 UnaryOp::Sigmoid,
1600 UnaryOp::Tanh,
1601 UnaryOp::Exp,
1602 UnaryOp::Log,
1603 UnaryOp::Sqrt,
1604 UnaryOp::Abs,
1605 UnaryOp::Neg,
1606 ];
1607 for op in ops {
1608 let words = unary_compute_shader(op);
1609 check_valid_spirv(&words);
1610 }
1611 }
1612
1613 #[test]
1614 fn binary_shader_all_ops() {
1615 let ops = [
1616 BinaryOp::Add,
1617 BinaryOp::Sub,
1618 BinaryOp::Mul,
1619 BinaryOp::Div,
1620 BinaryOp::Max,
1621 BinaryOp::Min,
1622 ];
1623 for op in ops {
1624 let words = binary_compute_shader(op);
1625 check_valid_spirv(&words);
1626 }
1627 }
1628
1629 #[test]
1630 fn reduce_shader_all_ops() {
1631 let ops = [ReduceOp::Sum, ReduceOp::Max, ReduceOp::Min, ReduceOp::Mean];
1632 for op in ops {
1633 let words = reduce_compute_shader(op);
1634 check_valid_spirv(&words);
1635 }
1636 }
1637
1638 #[test]
1639 fn gemm_shader_valid() {
1640 let words = gemm_compute_shader();
1641 check_valid_spirv(&words);
1642 }
1643
1644 #[test]
1645 fn batched_gemm_shader_valid() {
1646 let words = batched_gemm_compute_shader();
1647 check_valid_spirv(&words);
1648 }
1649
1650 #[test]
1651 fn batched_gemm_shader_word_aligned() {
1652 let words = batched_gemm_compute_shader();
1653 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
1654 assert_eq!(bytes.len() % 4, 0);
1655 }
1656
1657 #[test]
1658 fn batched_gemm_shader_uses_kernel_capability() {
1659 let words = batched_gemm_compute_shader();
1660 let cap_header = (2u32 << 16) | OP_CAPABILITY;
1661 assert_eq!(words[5], cap_header);
1662 assert_eq!(words[6], 6); }
1664
1665 #[test]
1666 fn all_kernel_shaders_word_aligned() {
1667 fn to_bytes(words: &[u32]) -> Vec<u8> {
1668 words.iter().flat_map(|w| w.to_ne_bytes()).collect()
1669 }
1670 assert_eq!(to_bytes(&unary_compute_shader(UnaryOp::Relu)).len() % 4, 0);
1671 assert_eq!(to_bytes(&binary_compute_shader(BinaryOp::Add)).len() % 4, 0);
1672 assert_eq!(to_bytes(&reduce_compute_shader(ReduceOp::Sum)).len() % 4, 0);
1673 assert_eq!(to_bytes(&gemm_compute_shader()).len() % 4, 0);
1674 assert_eq!(to_bytes(&batched_gemm_compute_shader()).len() % 4, 0);
1675 }
1676
1677 #[test]
1678 fn kernel_shaders_use_opencl_memory_model() {
1679 let trivial = trivial_compute_shader();
1682 let unary = unary_compute_shader(UnaryOp::Relu);
1683
1684 let cap_header = (2u32 << 16) | OP_CAPABILITY;
1691 assert_eq!(trivial[5], cap_header);
1692 assert_eq!(trivial[6], CAPABILITY_SHADER);
1693 assert_eq!(unary[5], cap_header);
1694 assert_eq!(unary[6], CAPABILITY_KERNEL);
1695 }
1696
1697 fn decode_insts(words: &[u32]) -> Vec<(u32, Vec<u32>)> {
1701 let mut out = Vec::new();
1702 let mut i = 5;
1703 while i < words.len() {
1704 let wc = (words[i] >> 16) as usize;
1705 let op = words[i] & 0xffff;
1706 if wc == 0 || i + wc > words.len() {
1707 break;
1708 }
1709 out.push((op, words[i + 1..i + wc].to_vec()));
1710 i += wc;
1711 }
1712 out
1713 }
1714
1715 #[test]
1716 fn ext_math_fn_metadata() {
1717 assert_eq!(ExtMathFn::Erf.opencl_inst(), 18);
1718 assert_eq!(ExtMathFn::Erfc.opencl_inst(), 17);
1719 assert_eq!(ExtMathFn::Atan.opencl_inst(), 9);
1720 assert_eq!(ExtMathFn::Erf.name(), "erf");
1721 assert_eq!(ExtMathFn::Rsqrt.name(), "rsqrt");
1722 }
1723
1724 #[test]
1725 fn ext_math_all_fns_valid() {
1726 let fns = [
1727 ExtMathFn::Erf,
1728 ExtMathFn::Erfc,
1729 ExtMathFn::Sin,
1730 ExtMathFn::Cos,
1731 ExtMathFn::Atan,
1732 ExtMathFn::Tanh,
1733 ExtMathFn::Cbrt,
1734 ExtMathFn::Rsqrt,
1735 ];
1736 for f in fns {
1737 let words = ext_math_compute_shader(f);
1738 check_valid_spirv(&words);
1739 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
1740 assert_eq!(bytes.len() % 4, 0);
1741 let insts = decode_insts(&words);
1743 let has_inst = insts
1744 .iter()
1745 .any(|(op, ops)| *op == OP_EXT_INST && ops.get(3) == Some(&f.opencl_inst()));
1746 assert!(has_inst, "missing OpExtInst for {}", f.name());
1747 }
1748 }
1749
1750 #[test]
1751 fn atan2_shader_emits_two_arg_ext_inst() {
1752 let words = atan2_compute_shader();
1753 check_valid_spirv(&words);
1754 let insts = decode_insts(&words);
1755 let atan2_inst = insts
1757 .iter()
1758 .find(|(op, ops)| *op == OP_EXT_INST && ops.get(3) == Some(&11));
1759 let (_, ops) = atan2_inst.expect("atan2 OpExtInst present");
1760 assert_eq!(ops.len(), 6, "atan2 must pass two arguments");
1762 }
1763}