1use crate::spirv::{
16 EXECUTION_MODEL_KERNEL, FUNCTION_CONTROL_NONE, OP_COMPOSITE_EXTRACT, OP_CONTROL_BARRIER,
17 OP_F_ADD, OP_F_MUL, OP_GROUP_NON_UNIFORM_FADD, OP_GROUP_NON_UNIFORM_SHUFFLE, OP_I_ADD,
18 OP_I_MUL, OP_PHI, OP_TYPE_ARRAY, OP_U_LESS_THAN, STORAGE_CLASS_FUNCTION, SpvModule,
19 WORKGROUP_SIZE,
20};
21
22const CAPABILITY_ADDRESSES: u32 = 4;
26const CAPABILITY_KERNEL: u32 = 6;
27const CAPABILITY_GROUP_NON_UNIFORM: u32 = 61;
28const CAPABILITY_GROUP_NON_UNIFORM_ARITHMETIC: u32 = 63;
29const CAPABILITY_GROUP_NON_UNIFORM_SHUFFLE: u32 = 65;
30
31const ADDRESSING_MODEL_PHYSICAL64: u32 = 2;
33const MEMORY_MODEL_OPENCL: u32 = 2;
34
35const DECORATION_BUILTIN: u32 = 11;
37
38const BUILTIN_GLOBAL_INVOCATION_ID: u32 = 28;
40const BUILTIN_NUM_SUBGROUPS: u32 = 38;
41const BUILTIN_SUBGROUP_ID: u32 = 40;
42const BUILTIN_SUBGROUP_LOCAL_INVOCATION_ID: u32 = 41;
43
44const STORAGE_CLASS_INPUT: u32 = 1;
46const STORAGE_CLASS_WORKGROUP: u32 = 4;
47const STORAGE_CLASS_CROSS_WORKGROUP: u32 = 5;
48
49const SCOPE_WORKGROUP: u32 = 2;
51const SCOPE_SUBGROUP: u32 = 3;
52
53const MEMORY_SEMANTICS_WORKGROUP_MEMORY: u32 = 0x100;
55
56const GROUP_OPERATION_REDUCE: u32 = 0;
58const GROUP_OPERATION_INCLUSIVE_SCAN: u32 = 1;
59
60const OP_I_EQUAL: u32 = 170;
62
63const MAX_SUBGROUPS: u32 = 32;
65
66pub fn reduction_subgroup_spirv() -> Vec<u32> {
79 let mut m = SpvModule::new();
80
81 m.emit_capability(CAPABILITY_KERNEL);
83 m.emit_capability(CAPABILITY_ADDRESSES);
84 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM);
85 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM_ARITHMETIC);
86
87 let opencl_ext = m.alloc_id();
89 m.emit_ext_inst_import(opencl_ext, "OpenCL.std");
90
91 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
93
94 let ty_void = m.alloc_id();
96 let ty_bool = m.alloc_id();
97 let ty_uint = m.alloc_id();
98 let ty_float = m.alloc_id();
99 let ty_v3uint = m.alloc_id();
100 let ty_ptr_input_v3uint = m.alloc_id();
101 let ty_ptr_cross_float = m.alloc_id();
102 let ty_ptr_func_float = m.alloc_id();
103 let ty_ptr_func_uint = m.alloc_id();
104 let ty_ptr_wg_float = m.alloc_id();
105 let ty_ptr_input_uint = m.alloc_id();
106 let ty_arr_float = m.alloc_id();
107 let ty_ptr_wg_arr = m.alloc_id();
108
109 let c_uint_0 = m.alloc_id();
111 let c_uint_1 = m.alloc_id();
112 let c_uint_max_sg = m.alloc_id();
113 let c_float_0 = m.alloc_id();
114 let c_scope_sg = m.alloc_id();
115 let c_scope_wg = m.alloc_id();
116 let c_mem_sem = m.alloc_id();
117
118 let var_gid = m.alloc_id();
120 let var_sg_id = m.alloc_id();
121 let var_sg_lid = m.alloc_id();
122 let var_num_sg = m.alloc_id();
123 let var_scratch = m.alloc_id();
124 let var_j = m.alloc_id();
126 let var_acc = m.alloc_id();
127
128 let fn_ty = m.alloc_id();
130 let main_fn = m.alloc_id();
131 let p_input = m.alloc_id();
132 let p_output = m.alloc_id();
133 let p_count = m.alloc_id();
134
135 m.emit_entry_point(
137 EXECUTION_MODEL_KERNEL,
138 main_fn,
139 "reduction_subgroup",
140 &[var_gid, var_sg_id, var_sg_lid, var_num_sg],
141 );
142 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
143
144 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
146 m.emit_decorate(var_sg_id, DECORATION_BUILTIN, &[BUILTIN_SUBGROUP_ID]);
147 m.emit_decorate(
148 var_sg_lid,
149 DECORATION_BUILTIN,
150 &[BUILTIN_SUBGROUP_LOCAL_INVOCATION_ID],
151 );
152 m.emit_decorate(var_num_sg, DECORATION_BUILTIN, &[BUILTIN_NUM_SUBGROUPS]);
153
154 m.emit_type_void(ty_void);
156 m.emit_type_bool(ty_bool);
157 m.emit_type_int(ty_uint, 32, 0);
158 m.emit_type_float(ty_float, 32);
159 m.emit_type_vector(ty_v3uint, ty_uint, 3);
160 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
161 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
162 m.emit_type_pointer(ty_ptr_func_float, STORAGE_CLASS_FUNCTION, ty_float);
163 m.emit_type_pointer(ty_ptr_func_uint, STORAGE_CLASS_FUNCTION, ty_uint);
164 m.emit_type_pointer(ty_ptr_wg_float, STORAGE_CLASS_WORKGROUP, ty_float);
165 m.emit_type_pointer(ty_ptr_input_uint, STORAGE_CLASS_INPUT, ty_uint);
166 m.emit_type_function(
167 fn_ty,
168 ty_void,
169 &[ty_ptr_cross_float, ty_ptr_cross_float, ty_uint],
170 );
171
172 m.emit_constant_u32(ty_uint, c_uint_0, 0);
174 m.emit_constant_u32(ty_uint, c_uint_1, 1);
175 m.emit_constant_u32(ty_uint, c_uint_max_sg, MAX_SUBGROUPS);
176 m.emit_constant_f32(ty_float, c_float_0, 0.0);
177 m.emit_constant_u32(ty_uint, c_scope_sg, SCOPE_SUBGROUP);
178 m.emit_constant_u32(ty_uint, c_scope_wg, SCOPE_WORKGROUP);
179 m.emit_constant_u32(ty_uint, c_mem_sem, MEMORY_SEMANTICS_WORKGROUP_MEMORY);
180
181 m.emit(OP_TYPE_ARRAY, &[ty_arr_float, ty_float, c_uint_max_sg]);
183 m.emit_type_pointer(ty_ptr_wg_arr, STORAGE_CLASS_WORKGROUP, ty_arr_float);
184
185 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
187 m.emit_variable(ty_ptr_input_uint, var_sg_id, STORAGE_CLASS_INPUT);
188 m.emit_variable(ty_ptr_input_uint, var_sg_lid, STORAGE_CLASS_INPUT);
189 m.emit_variable(ty_ptr_input_uint, var_num_sg, STORAGE_CLASS_INPUT);
190 m.emit_variable(ty_ptr_wg_arr, var_scratch, STORAGE_CLASS_WORKGROUP);
191
192 let label_entry = m.alloc_id();
194 let label_bounds_body = m.alloc_id();
195 let label_bounds_merge = m.alloc_id();
196 let label_leader = m.alloc_id();
197 let label_after_leader = m.alloc_id();
198 let label_sg0 = m.alloc_id();
199 let label_after_sg0 = m.alloc_id();
200 let label_sum_hdr = m.alloc_id();
201 let label_sum_body = m.alloc_id();
202 let label_sum_cont = m.alloc_id();
203 let label_sum_merge = m.alloc_id();
204
205 m.emit_function(ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
207 m.emit_function_parameter(ty_ptr_cross_float, p_input);
208 m.emit_function_parameter(ty_ptr_cross_float, p_output);
209 m.emit_function_parameter(ty_uint, p_count);
210 m.emit_label(label_entry);
211
212 m.emit_variable(ty_ptr_func_uint, var_j, STORAGE_CLASS_FUNCTION);
214 m.emit_variable(ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
215
216 let gid_val = m.alloc_id();
218 m.emit_load(ty_v3uint, gid_val, var_gid);
219 let gid_x = m.alloc_id();
220 m.emit(OP_COMPOSITE_EXTRACT, &[ty_uint, gid_x, gid_val, 0]);
221
222 let sg_id = m.alloc_id();
224 m.emit_load(ty_uint, sg_id, var_sg_id);
225 let sg_lid = m.alloc_id();
226 m.emit_load(ty_uint, sg_lid, var_sg_lid);
227 let num_sg = m.alloc_id();
228 m.emit_load(ty_uint, num_sg, var_num_sg);
229
230 let cond_bounds = m.alloc_id();
232 m.emit(OP_U_LESS_THAN, &[ty_bool, cond_bounds, gid_x, p_count]);
233
234 m.emit_selection_merge(label_bounds_merge);
236 m.emit_branch_conditional(cond_bounds, label_bounds_body, label_bounds_merge);
237
238 m.emit_label(label_bounds_body);
239 let inp_ptr = m.alloc_id();
240 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, inp_ptr, p_input, gid_x);
241 let inp_val = m.alloc_id();
242 m.emit_load(ty_float, inp_val, inp_ptr);
243 m.emit_branch(label_bounds_merge);
244
245 m.emit_label(label_bounds_merge);
246 let val = m.alloc_id();
248 m.emit(
249 OP_PHI,
250 &[
251 ty_float,
252 val,
253 inp_val,
254 label_bounds_body,
255 c_float_0,
256 label_entry,
257 ],
258 );
259
260 let sg_sum = m.alloc_id();
262 m.emit(
263 OP_GROUP_NON_UNIFORM_FADD,
264 &[ty_float, sg_sum, c_scope_sg, GROUP_OPERATION_REDUCE, val],
265 );
266
267 let is_leader_eq = m.alloc_id();
269 m.emit(OP_I_EQUAL, &[ty_bool, is_leader_eq, sg_lid, c_uint_0]);
270
271 m.emit_selection_merge(label_after_leader);
272 m.emit_branch_conditional(is_leader_eq, label_leader, label_after_leader);
273
274 m.emit_label(label_leader);
275 let scratch_ptr = m.alloc_id();
276 m.emit_in_bounds_ptr_access_chain(ty_ptr_wg_float, scratch_ptr, var_scratch, sg_id);
277 m.emit_store(scratch_ptr, sg_sum);
278 m.emit_branch(label_after_leader);
279
280 m.emit_label(label_after_leader);
281
282 m.emit(OP_CONTROL_BARRIER, &[c_scope_wg, c_scope_wg, c_mem_sem]);
284
285 let is_sg0 = m.alloc_id();
292 m.emit(OP_I_EQUAL, &[ty_bool, is_sg0, sg_id, c_uint_0]);
293 let is_lane0 = m.alloc_id();
294 m.emit(OP_I_EQUAL, &[ty_bool, is_lane0, sg_lid, c_uint_0]);
295 let is_writer = m.alloc_id();
296 m.emit(167, &[ty_bool, is_writer, is_sg0, is_lane0]);
298
299 m.emit_selection_merge(label_after_sg0);
300 m.emit_branch_conditional(is_writer, label_sg0, label_after_sg0);
301
302 m.emit_label(label_sg0);
303
304 m.emit_store(var_j, c_uint_0);
306 m.emit_store(var_acc, c_float_0);
307
308 m.emit_branch(label_sum_hdr);
309 m.emit_label(label_sum_hdr);
310 let j_val = m.alloc_id();
311 m.emit_load(ty_uint, j_val, var_j);
312 let sum_cond = m.alloc_id();
313 m.emit(OP_U_LESS_THAN, &[ty_bool, sum_cond, j_val, num_sg]);
314 m.emit_loop_merge(label_sum_merge, label_sum_cont);
315 m.emit_branch_conditional(sum_cond, label_sum_body, label_sum_merge);
316
317 m.emit_label(label_sum_body);
318 let s_ptr = m.alloc_id();
319 m.emit_in_bounds_ptr_access_chain(ty_ptr_wg_float, s_ptr, var_scratch, j_val);
320 let partial = m.alloc_id();
321 m.emit_load(ty_float, partial, s_ptr);
322 let old_acc = m.alloc_id();
323 m.emit_load(ty_float, old_acc, var_acc);
324 let new_acc = m.alloc_id();
325 m.emit(OP_F_ADD, &[ty_float, new_acc, old_acc, partial]);
326 m.emit_store(var_acc, new_acc);
327 m.emit_branch(label_sum_cont);
328
329 m.emit_label(label_sum_cont);
330 let j_inc = m.alloc_id();
331 m.emit(OP_I_ADD, &[ty_uint, j_inc, j_val, c_uint_1]);
332 m.emit_store(var_j, j_inc);
333 m.emit_branch(label_sum_hdr);
334
335 m.emit_label(label_sum_merge);
336 let final_sum = m.alloc_id();
337 m.emit_load(ty_float, final_sum, var_acc);
338 let out_ptr = m.alloc_id();
339 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, out_ptr, p_output, c_uint_0);
340 m.emit_store(out_ptr, final_sum);
341 m.emit_branch(label_after_sg0);
342
343 m.emit_label(label_after_sg0);
344 m.emit_return();
345 m.emit_function_end();
346
347 m.finalize()
348}
349
350pub fn scan_subgroup_spirv() -> Vec<u32> {
361 let mut m = SpvModule::new();
362
363 m.emit_capability(CAPABILITY_KERNEL);
365 m.emit_capability(CAPABILITY_ADDRESSES);
366 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM);
367 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM_ARITHMETIC);
368
369 let opencl_ext = m.alloc_id();
371 m.emit_ext_inst_import(opencl_ext, "OpenCL.std");
372
373 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
375
376 let ty_void = m.alloc_id();
378 let ty_bool = m.alloc_id();
379 let ty_uint = m.alloc_id();
380 let ty_float = m.alloc_id();
381 let ty_v3uint = m.alloc_id();
382 let ty_ptr_input_v3uint = m.alloc_id();
383 let ty_ptr_cross_float = m.alloc_id();
384 let ty_ptr_input_uint = m.alloc_id();
385 let ty_arr_float = m.alloc_id();
386 let ty_ptr_wg_float = m.alloc_id();
387 let ty_ptr_wg_arr = m.alloc_id();
388
389 let c_uint_0 = m.alloc_id();
391 let c_uint_1 = m.alloc_id();
392 let c_uint_max_sg = m.alloc_id();
393 let c_float_0 = m.alloc_id();
394 let c_scope_sg = m.alloc_id();
395 let c_scope_wg = m.alloc_id();
396 let c_mem_sem = m.alloc_id();
397
398 let var_gid = m.alloc_id();
400 let var_sg_id = m.alloc_id();
401 let var_sg_lid = m.alloc_id();
402 let var_num_sg = m.alloc_id();
403 let var_scratch = m.alloc_id();
404
405 let fn_ty = m.alloc_id();
407 let main_fn = m.alloc_id();
408 let p_input = m.alloc_id();
409 let p_output = m.alloc_id();
410 let p_count = m.alloc_id();
411
412 m.emit_entry_point(
414 EXECUTION_MODEL_KERNEL,
415 main_fn,
416 "scan_subgroup",
417 &[var_gid, var_sg_id, var_sg_lid, var_num_sg],
418 );
419 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
420
421 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
423 m.emit_decorate(var_sg_id, DECORATION_BUILTIN, &[BUILTIN_SUBGROUP_ID]);
424 m.emit_decorate(
425 var_sg_lid,
426 DECORATION_BUILTIN,
427 &[BUILTIN_SUBGROUP_LOCAL_INVOCATION_ID],
428 );
429 m.emit_decorate(var_num_sg, DECORATION_BUILTIN, &[BUILTIN_NUM_SUBGROUPS]);
430
431 m.emit_type_void(ty_void);
433 m.emit_type_bool(ty_bool);
434 m.emit_type_int(ty_uint, 32, 0);
435 m.emit_type_float(ty_float, 32);
436 m.emit_type_vector(ty_v3uint, ty_uint, 3);
437 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
438 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
439 m.emit_type_pointer(ty_ptr_input_uint, STORAGE_CLASS_INPUT, ty_uint);
440 m.emit_type_pointer(ty_ptr_wg_float, STORAGE_CLASS_WORKGROUP, ty_float);
441 m.emit_type_function(
442 fn_ty,
443 ty_void,
444 &[ty_ptr_cross_float, ty_ptr_cross_float, ty_uint],
445 );
446
447 m.emit_constant_u32(ty_uint, c_uint_0, 0);
449 m.emit_constant_u32(ty_uint, c_uint_1, 1);
450 m.emit_constant_u32(ty_uint, c_uint_max_sg, MAX_SUBGROUPS);
451 m.emit_constant_f32(ty_float, c_float_0, 0.0);
452 m.emit_constant_u32(ty_uint, c_scope_sg, SCOPE_SUBGROUP);
453 m.emit_constant_u32(ty_uint, c_scope_wg, SCOPE_WORKGROUP);
454 m.emit_constant_u32(ty_uint, c_mem_sem, MEMORY_SEMANTICS_WORKGROUP_MEMORY);
455
456 m.emit(OP_TYPE_ARRAY, &[ty_arr_float, ty_float, c_uint_max_sg]);
458 m.emit_type_pointer(ty_ptr_wg_arr, STORAGE_CLASS_WORKGROUP, ty_arr_float);
459
460 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
462 m.emit_variable(ty_ptr_input_uint, var_sg_id, STORAGE_CLASS_INPUT);
463 m.emit_variable(ty_ptr_input_uint, var_sg_lid, STORAGE_CLASS_INPUT);
464 m.emit_variable(ty_ptr_input_uint, var_num_sg, STORAGE_CLASS_INPUT);
465 m.emit_variable(ty_ptr_wg_arr, var_scratch, STORAGE_CLASS_WORKGROUP);
466
467 let label_entry = m.alloc_id();
469 let label_bounds_body = m.alloc_id();
470 let label_bounds_merge = m.alloc_id();
471 let label_leader = m.alloc_id();
472 let label_after_leader = m.alloc_id();
473 let label_add_prefix = m.alloc_id();
474 let label_after_add = m.alloc_id();
475 let label_write = m.alloc_id();
476 let label_end = m.alloc_id();
477
478 m.emit_function(ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
480 m.emit_function_parameter(ty_ptr_cross_float, p_input);
481 m.emit_function_parameter(ty_ptr_cross_float, p_output);
482 m.emit_function_parameter(ty_uint, p_count);
483 m.emit_label(label_entry);
484
485 let gid_val = m.alloc_id();
487 m.emit_load(ty_v3uint, gid_val, var_gid);
488 let gid_x = m.alloc_id();
489 m.emit(OP_COMPOSITE_EXTRACT, &[ty_uint, gid_x, gid_val, 0]);
490
491 let sg_id = m.alloc_id();
493 m.emit_load(ty_uint, sg_id, var_sg_id);
494 let sg_lid = m.alloc_id();
495 m.emit_load(ty_uint, sg_lid, var_sg_lid);
496
497 let in_bounds = m.alloc_id();
499 m.emit(OP_U_LESS_THAN, &[ty_bool, in_bounds, gid_x, p_count]);
500 m.emit_selection_merge(label_bounds_merge);
501 m.emit_branch_conditional(in_bounds, label_bounds_body, label_bounds_merge);
502
503 m.emit_label(label_bounds_body);
504 let inp_ptr = m.alloc_id();
505 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, inp_ptr, p_input, gid_x);
506 let inp_val = m.alloc_id();
507 m.emit_load(ty_float, inp_val, inp_ptr);
508 m.emit_branch(label_bounds_merge);
509
510 m.emit_label(label_bounds_merge);
511 let val = m.alloc_id();
512 m.emit(
513 OP_PHI,
514 &[
515 ty_float,
516 val,
517 inp_val,
518 label_bounds_body,
519 c_float_0,
520 label_entry,
521 ],
522 );
523
524 let sg_scan = m.alloc_id();
526 m.emit(
527 OP_GROUP_NON_UNIFORM_FADD,
528 &[
529 ty_float,
530 sg_scan,
531 c_scope_sg,
532 GROUP_OPERATION_INCLUSIVE_SCAN,
533 val,
534 ],
535 );
536
537 let sg_total = m.alloc_id();
539 m.emit(
540 OP_GROUP_NON_UNIFORM_FADD,
541 &[ty_float, sg_total, c_scope_sg, GROUP_OPERATION_REDUCE, val],
542 );
543
544 let is_leader = m.alloc_id();
546 m.emit(OP_I_EQUAL, &[ty_bool, is_leader, sg_lid, c_uint_0]);
547
548 m.emit_selection_merge(label_after_leader);
549 m.emit_branch_conditional(is_leader, label_leader, label_after_leader);
550
551 m.emit_label(label_leader);
552 let scratch_ptr = m.alloc_id();
553 m.emit_in_bounds_ptr_access_chain(ty_ptr_wg_float, scratch_ptr, var_scratch, sg_id);
554 m.emit_store(scratch_ptr, sg_total);
555 m.emit_branch(label_after_leader);
556
557 m.emit_label(label_after_leader);
558
559 m.emit(OP_CONTROL_BARRIER, &[c_scope_wg, c_scope_wg, c_mem_sem]);
561
562 let has_prefix = m.alloc_id();
565 m.emit(OP_U_LESS_THAN, &[ty_bool, has_prefix, c_uint_0, sg_id]); m.emit_selection_merge(label_after_add);
568 m.emit_branch_conditional(has_prefix, label_add_prefix, label_after_add);
569
570 m.emit_label(label_add_prefix);
571
572 let var_j = m.alloc_id();
574 let ty_ptr_func_uint = m.alloc_id();
575 m.emit_type_pointer(ty_ptr_func_uint, STORAGE_CLASS_FUNCTION, ty_uint);
576 let var_prefix_acc = m.alloc_id();
577 let ty_ptr_func_float = m.alloc_id();
578 m.emit_type_pointer(ty_ptr_func_float, STORAGE_CLASS_FUNCTION, ty_float);
579 m.emit_variable(ty_ptr_func_uint, var_j, STORAGE_CLASS_FUNCTION);
580 m.emit_variable(ty_ptr_func_float, var_prefix_acc, STORAGE_CLASS_FUNCTION);
581 m.emit_store(var_j, c_uint_0);
582 m.emit_store(var_prefix_acc, c_float_0);
583
584 let lbl_loop_hdr = m.alloc_id();
585 let lbl_loop_body = m.alloc_id();
586 let lbl_loop_cont = m.alloc_id();
587 let lbl_loop_merge = m.alloc_id();
588
589 m.emit_branch(lbl_loop_hdr);
590 m.emit_label(lbl_loop_hdr);
591 let j_val = m.alloc_id();
592 m.emit_load(ty_uint, j_val, var_j);
593 let loop_cond = m.alloc_id();
594 m.emit(OP_U_LESS_THAN, &[ty_bool, loop_cond, j_val, sg_id]);
595 m.emit_loop_merge(lbl_loop_merge, lbl_loop_cont);
596 m.emit_branch_conditional(loop_cond, lbl_loop_body, lbl_loop_merge);
597
598 m.emit_label(lbl_loop_body);
599 let s_ptr = m.alloc_id();
600 m.emit_in_bounds_ptr_access_chain(ty_ptr_wg_float, s_ptr, var_scratch, j_val);
601 let s_val = m.alloc_id();
602 m.emit_load(ty_float, s_val, s_ptr);
603 let old_prefix = m.alloc_id();
604 m.emit_load(ty_float, old_prefix, var_prefix_acc);
605 let new_prefix = m.alloc_id();
606 m.emit(OP_F_ADD, &[ty_float, new_prefix, old_prefix, s_val]);
607 m.emit_store(var_prefix_acc, new_prefix);
608 m.emit_branch(lbl_loop_cont);
609
610 m.emit_label(lbl_loop_cont);
611 let j_inc = m.alloc_id();
612 m.emit(OP_I_ADD, &[ty_uint, j_inc, j_val, c_uint_1]);
613 m.emit_store(var_j, j_inc);
614 m.emit_branch(lbl_loop_hdr);
615
616 m.emit_label(lbl_loop_merge);
617 let prefix_val = m.alloc_id();
618 m.emit_load(ty_float, prefix_val, var_prefix_acc);
619 m.emit_branch(label_after_add);
620
621 m.emit_label(label_after_add);
622 let prefix = m.alloc_id();
624 m.emit(
625 OP_PHI,
626 &[
627 ty_float,
628 prefix,
629 prefix_val,
630 lbl_loop_merge,
631 c_float_0,
632 label_after_leader,
633 ],
634 );
635
636 let final_val = m.alloc_id();
638 m.emit(OP_F_ADD, &[ty_float, final_val, sg_scan, prefix]);
639
640 m.emit_selection_merge(label_end);
642 m.emit_branch_conditional(in_bounds, label_write, label_end);
643
644 m.emit_label(label_write);
645 let out_ptr = m.alloc_id();
646 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, out_ptr, p_output, gid_x);
647 m.emit_store(out_ptr, final_val);
648 m.emit_branch(label_end);
649
650 m.emit_label(label_end);
651 m.emit_return();
652 m.emit_function_end();
653
654 m.finalize()
655}
656
657pub fn gemm_subgroup_spirv() -> Vec<u32> {
674 let mut m = SpvModule::new();
675
676 m.emit_capability(CAPABILITY_KERNEL);
678 m.emit_capability(CAPABILITY_ADDRESSES);
679 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM);
680 m.emit_capability(CAPABILITY_GROUP_NON_UNIFORM_SHUFFLE);
681
682 let opencl_ext = m.alloc_id();
684 m.emit_ext_inst_import(opencl_ext, "OpenCL.std");
685
686 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
688
689 let ty_void = m.alloc_id();
691 let ty_bool = m.alloc_id();
692 let ty_uint = m.alloc_id();
693 let ty_float = m.alloc_id();
694 let ty_v3uint = m.alloc_id();
695 let ty_ptr_input_v3uint = m.alloc_id();
696 let ty_ptr_cross_float = m.alloc_id();
697 let ty_ptr_func_float = m.alloc_id();
698 let ty_ptr_func_uint = m.alloc_id();
699 let ty_ptr_input_uint = m.alloc_id();
700
701 let c_uint_0 = m.alloc_id();
703 let c_uint_1 = m.alloc_id();
704 let c_float_0 = m.alloc_id();
705 let c_scope_sg = m.alloc_id();
706
707 let var_gid = m.alloc_id();
709 let var_sg_lid = m.alloc_id();
710
711 let fn_ty = m.alloc_id();
713 let main_fn = m.alloc_id();
714 let p_a = m.alloc_id();
715 let p_b = m.alloc_id();
716 let p_c = m.alloc_id();
717 let p_m = m.alloc_id();
718 let p_n = m.alloc_id();
719 let p_k = m.alloc_id();
720 let p_alpha = m.alloc_id();
721 let p_beta = m.alloc_id();
722
723 m.emit_entry_point(
725 EXECUTION_MODEL_KERNEL,
726 main_fn,
727 "gemm_subgroup",
728 &[var_gid, var_sg_lid],
729 );
730 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
731
732 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
734 m.emit_decorate(
735 var_sg_lid,
736 DECORATION_BUILTIN,
737 &[BUILTIN_SUBGROUP_LOCAL_INVOCATION_ID],
738 );
739
740 m.emit_type_void(ty_void);
742 m.emit_type_bool(ty_bool);
743 m.emit_type_int(ty_uint, 32, 0);
744 m.emit_type_float(ty_float, 32);
745 m.emit_type_vector(ty_v3uint, ty_uint, 3);
746 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
747 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
748 m.emit_type_pointer(ty_ptr_func_float, STORAGE_CLASS_FUNCTION, ty_float);
749 m.emit_type_pointer(ty_ptr_func_uint, STORAGE_CLASS_FUNCTION, ty_uint);
750 m.emit_type_pointer(ty_ptr_input_uint, STORAGE_CLASS_INPUT, ty_uint);
751 m.emit_type_function(
752 fn_ty,
753 ty_void,
754 &[
755 ty_ptr_cross_float,
756 ty_ptr_cross_float,
757 ty_ptr_cross_float,
758 ty_uint,
759 ty_uint,
760 ty_uint,
761 ty_float,
762 ty_float,
763 ],
764 );
765
766 m.emit_constant_u32(ty_uint, c_uint_0, 0);
768 m.emit_constant_u32(ty_uint, c_uint_1, 1);
769 m.emit_constant_f32(ty_float, c_float_0, 0.0);
770 m.emit_constant_u32(ty_uint, c_scope_sg, SCOPE_SUBGROUP);
771
772 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
774 m.emit_variable(ty_ptr_input_uint, var_sg_lid, STORAGE_CLASS_INPUT);
775
776 let label_entry = m.alloc_id();
778 let label_bounds_body = m.alloc_id();
779 let label_bounds_merge = m.alloc_id();
780 let label_loop_header = m.alloc_id();
781 let label_loop_body = m.alloc_id();
782 let label_loop_continue = m.alloc_id();
783 let label_loop_merge = m.alloc_id();
784
785 m.emit_function(ty_void, main_fn, FUNCTION_CONTROL_NONE, fn_ty);
787 m.emit_function_parameter(ty_ptr_cross_float, p_a);
788 m.emit_function_parameter(ty_ptr_cross_float, p_b);
789 m.emit_function_parameter(ty_ptr_cross_float, p_c);
790 m.emit_function_parameter(ty_uint, p_m);
791 m.emit_function_parameter(ty_uint, p_n);
792 m.emit_function_parameter(ty_uint, p_k);
793 m.emit_function_parameter(ty_float, p_alpha);
794 m.emit_function_parameter(ty_float, p_beta);
795 m.emit_label(label_entry);
796
797 let gid_val = m.alloc_id();
799 m.emit_load(ty_v3uint, gid_val, var_gid);
800 let gid_x = m.alloc_id();
801 m.emit(OP_COMPOSITE_EXTRACT, &[ty_uint, gid_x, gid_val, 0]);
802
803 let sg_lid = m.alloc_id();
805 m.emit_load(ty_uint, sg_lid, var_sg_lid);
806
807 let total = m.alloc_id();
809 m.emit(OP_I_MUL, &[ty_uint, total, p_m, p_n]);
810
811 let cond = m.alloc_id();
813 m.emit(OP_U_LESS_THAN, &[ty_bool, cond, gid_x, total]);
814 m.emit_selection_merge(label_bounds_merge);
815 m.emit_branch_conditional(cond, label_bounds_body, label_bounds_merge);
816
817 m.emit_label(label_bounds_body);
818
819 let row = m.alloc_id();
822 m.emit(134, &[ty_uint, row, gid_x, p_n]); let col = m.alloc_id();
824 m.emit(137, &[ty_uint, col, gid_x, p_n]); let var_acc = m.alloc_id();
828 m.emit_variable(ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
829 m.emit_store(var_acc, c_float_0);
830 let var_i = m.alloc_id();
831 m.emit_variable(ty_ptr_func_uint, var_i, STORAGE_CLASS_FUNCTION);
832 m.emit_store(var_i, c_uint_0);
833
834 m.emit_branch(label_loop_header);
835
836 m.emit_label(label_loop_header);
838 let i_val = m.alloc_id();
839 m.emit_load(ty_uint, i_val, var_i);
840 let loop_cond = m.alloc_id();
841 m.emit(OP_U_LESS_THAN, &[ty_bool, loop_cond, i_val, p_k]);
842 m.emit_loop_merge(label_loop_merge, label_loop_continue);
843 m.emit_branch_conditional(loop_cond, label_loop_body, label_loop_merge);
844
845 m.emit_label(label_loop_body);
847
848 let a_idx = m.alloc_id();
850 let row_k = m.alloc_id();
851 m.emit(OP_I_MUL, &[ty_uint, row_k, row, p_k]);
852 m.emit(OP_I_ADD, &[ty_uint, a_idx, row_k, i_val]);
853 let a_ptr = m.alloc_id();
854 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, a_ptr, p_a, a_idx);
855 let a_val = m.alloc_id();
856 m.emit_load(ty_float, a_val, a_ptr);
857
858 let a_broadcast = m.alloc_id();
860 m.emit(
861 OP_GROUP_NON_UNIFORM_SHUFFLE,
862 &[ty_float, a_broadcast, c_scope_sg, a_val, sg_lid],
863 );
864
865 let b_idx = m.alloc_id();
867 let i_n = m.alloc_id();
868 m.emit(OP_I_MUL, &[ty_uint, i_n, i_val, p_n]);
869 m.emit(OP_I_ADD, &[ty_uint, b_idx, i_n, col]);
870 let b_ptr = m.alloc_id();
871 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, b_ptr, p_b, b_idx);
872 let b_val = m.alloc_id();
873 m.emit_load(ty_float, b_val, b_ptr);
874
875 let prod = m.alloc_id();
877 m.emit(OP_F_MUL, &[ty_float, prod, a_broadcast, b_val]);
878 let old_acc = m.alloc_id();
879 m.emit_load(ty_float, old_acc, var_acc);
880 let new_acc = m.alloc_id();
881 m.emit(OP_F_ADD, &[ty_float, new_acc, old_acc, prod]);
882 m.emit_store(var_acc, new_acc);
883
884 m.emit_branch(label_loop_continue);
885
886 m.emit_label(label_loop_continue);
888 let i_inc = m.alloc_id();
889 m.emit(OP_I_ADD, &[ty_uint, i_inc, i_val, c_uint_1]);
890 m.emit_store(var_i, i_inc);
891 m.emit_branch(label_loop_header);
892
893 m.emit_label(label_loop_merge);
895
896 let final_acc = m.alloc_id();
898 m.emit_load(ty_float, final_acc, var_acc);
899 let alpha_acc = m.alloc_id();
900 m.emit(OP_F_MUL, &[ty_float, alpha_acc, p_alpha, final_acc]);
901
902 let c_ptr = m.alloc_id();
903 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, c_ptr, p_c, gid_x);
904 let c_old = m.alloc_id();
905 m.emit_load(ty_float, c_old, c_ptr);
906 let beta_c = m.alloc_id();
907 m.emit(OP_F_MUL, &[ty_float, beta_c, p_beta, c_old]);
908 let c_new = m.alloc_id();
909 m.emit(OP_F_ADD, &[ty_float, c_new, alpha_acc, beta_c]);
910 m.emit_store(c_ptr, c_new);
911
912 m.emit_branch(label_bounds_merge);
913
914 m.emit_label(label_bounds_merge);
915 m.emit_return();
916 m.emit_function_end();
917
918 m.finalize()
919}
920
921#[cfg(test)]
924mod tests {
925 use super::*;
926 use crate::spirv::SPIRV_MAGIC;
927
928 const OP_CAPABILITY: u32 = 17;
929
930 fn check_valid_spirv(words: &[u32]) {
931 assert!(words.len() >= 5, "too short for SPIR-V header");
932 assert_eq!(words[0], SPIRV_MAGIC, "bad magic");
933 assert!(words[3] > 0, "ID bound must be > 0");
934 assert_eq!(words[4], 0, "schema must be 0");
935 }
936
937 fn has_capability(words: &[u32], cap: u32) -> bool {
939 let cap_header = (2u32 << 16) | OP_CAPABILITY;
940 words.windows(2).any(|w| w[0] == cap_header && w[1] == cap)
941 }
942
943 fn has_entry_point(words: &[u32], name: &str) -> bool {
945 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_le_bytes()).collect();
946 let name_bytes = name.as_bytes();
947 bytes.windows(name_bytes.len()).any(|w| w == name_bytes)
948 }
949
950 #[test]
951 fn reduction_subgroup_valid_spirv() {
952 let words = reduction_subgroup_spirv();
953 check_valid_spirv(&words);
954 }
955
956 #[test]
957 fn reduction_subgroup_word_aligned() {
958 let words = reduction_subgroup_spirv();
959 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
960 assert_eq!(bytes.len() % 4, 0);
961 }
962
963 #[test]
964 fn reduction_subgroup_has_group_non_uniform_capability() {
965 let words = reduction_subgroup_spirv();
966 assert!(
967 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM),
968 "missing GroupNonUniform capability"
969 );
970 assert!(
971 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM_ARITHMETIC),
972 "missing GroupNonUniformArithmetic capability"
973 );
974 }
975
976 #[test]
977 fn reduction_subgroup_has_entry_point() {
978 let words = reduction_subgroup_spirv();
979 assert!(
980 has_entry_point(&words, "reduction_subgroup"),
981 "missing entry point name"
982 );
983 }
984
985 #[test]
986 fn scan_subgroup_valid_spirv() {
987 let words = scan_subgroup_spirv();
988 check_valid_spirv(&words);
989 }
990
991 #[test]
992 fn scan_subgroup_word_aligned() {
993 let words = scan_subgroup_spirv();
994 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
995 assert_eq!(bytes.len() % 4, 0);
996 }
997
998 #[test]
999 fn scan_subgroup_has_group_non_uniform_capability() {
1000 let words = scan_subgroup_spirv();
1001 assert!(
1002 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM),
1003 "missing GroupNonUniform capability"
1004 );
1005 assert!(
1006 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM_ARITHMETIC),
1007 "missing GroupNonUniformArithmetic capability"
1008 );
1009 }
1010
1011 #[test]
1012 fn scan_subgroup_has_entry_point() {
1013 let words = scan_subgroup_spirv();
1014 assert!(
1015 has_entry_point(&words, "scan_subgroup"),
1016 "missing entry point name"
1017 );
1018 }
1019
1020 #[test]
1021 fn gemm_subgroup_valid_spirv() {
1022 let words = gemm_subgroup_spirv();
1023 check_valid_spirv(&words);
1024 }
1025
1026 #[test]
1027 fn gemm_subgroup_word_aligned() {
1028 let words = gemm_subgroup_spirv();
1029 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
1030 assert_eq!(bytes.len() % 4, 0);
1031 }
1032
1033 #[test]
1034 fn gemm_subgroup_has_group_non_uniform_capability() {
1035 let words = gemm_subgroup_spirv();
1036 assert!(
1037 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM),
1038 "missing GroupNonUniform capability"
1039 );
1040 assert!(
1041 has_capability(&words, CAPABILITY_GROUP_NON_UNIFORM_SHUFFLE),
1042 "missing GroupNonUniformShuffle capability"
1043 );
1044 }
1045
1046 #[test]
1047 fn gemm_subgroup_has_entry_point() {
1048 let words = gemm_subgroup_spirv();
1049 assert!(
1050 has_entry_point(&words, "gemm_subgroup"),
1051 "missing entry point name"
1052 );
1053 }
1054
1055 #[test]
1056 fn subgroup_shaders_all_word_aligned() {
1057 fn to_bytes(words: &[u32]) -> Vec<u8> {
1058 words.iter().flat_map(|w| w.to_ne_bytes()).collect()
1059 }
1060 assert_eq!(to_bytes(&reduction_subgroup_spirv()).len() % 4, 0);
1061 assert_eq!(to_bytes(&scan_subgroup_spirv()).len() % 4, 0);
1062 assert_eq!(to_bytes(&gemm_subgroup_spirv()).len() % 4, 0);
1063 }
1064}