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