1use rustc_hash::FxHashSet;
32use serde::{Deserialize, Serialize};
33
34use crate::{KernelBody, KernelDescriptor, KernelOpKind};
35
36pub type VerifyResult = Result<(), Vec<VerifyError>>;
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct VerifyError {
42 pub body_path: Vec<usize>,
43 pub op_index: usize,
44 pub kind: VerifyErrorKind,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48pub enum VerifyErrorKind {
49 DuplicateResultId(u32),
50 DanglingResultRef {
51 operand_pos: usize,
52 ref_id: u32,
53 },
54 LiteralPoolOutOfRange {
55 operand_pos: usize,
56 pool_idx: u32,
57 pool_size: usize,
58 },
59 ChildBodyIndexOutOfRange {
60 operand_pos: usize,
61 body_idx: u32,
62 child_count: usize,
63 },
64 LiteralOpMissingPoolOperand,
65 OperandCountTooShort {
66 expected_min: usize,
67 got: usize,
68 },
69 DispatchZeroDim {
72 axis: u8,
73 },
74 DuplicateBindingSlotId {
78 slot: u32,
79 },
80 HostBindingInWorkgroupRange {
88 slot: u32,
89 },
90 WorkgroupBindingInHostRange {
96 slot: u32,
97 },
98}
99
100#[must_use]
101pub fn verify(desc: &KernelDescriptor) -> VerifyResult {
102 use rustc_hash::FxHashSet;
103 let mut errors = Vec::new();
104 for (axis, &dim) in desc.dispatch.workgroup_size.iter().enumerate() {
106 if dim == 0 {
107 errors.push(VerifyError {
108 body_path: vec![],
109 op_index: 0,
110 kind: VerifyErrorKind::DispatchZeroDim { axis: axis as u8 },
111 });
112 }
113 }
114 use crate::descriptor::MemoryClass;
117 let mut seen_slots: FxHashSet<u32> = FxHashSet::default();
118 for s in &desc.bindings.slots {
119 if !seen_slots.insert(s.slot) {
120 errors.push(VerifyError {
121 body_path: vec![],
122 op_index: 0,
123 kind: VerifyErrorKind::DuplicateBindingSlotId { slot: s.slot },
124 });
125 }
126 let in_workgroup_range = s.slot >= crate::lower::WORKGROUP_SLOT_BASE;
127 let is_workgroup_class =
128 matches!(s.memory_class, MemoryClass::Shared | MemoryClass::Scratch,);
129 if in_workgroup_range && !is_workgroup_class {
130 errors.push(VerifyError {
131 body_path: vec![],
132 op_index: 0,
133 kind: VerifyErrorKind::HostBindingInWorkgroupRange { slot: s.slot },
134 });
135 }
136 if !in_workgroup_range && is_workgroup_class {
137 errors.push(VerifyError {
138 body_path: vec![],
139 op_index: 0,
140 kind: VerifyErrorKind::WorkgroupBindingInHostRange { slot: s.slot },
141 });
142 }
143 }
144 verify_body(
145 &desc.body,
146 &mut Vec::new(),
147 &FxHashSet::default(),
148 &mut errors,
149 );
150 if errors.is_empty() {
151 Ok(())
152 } else {
153 Err(errors)
154 }
155}
156
157fn verify_body(
158 body: &KernelBody,
159 path: &mut Vec<usize>,
160 inherited_results: &FxHashSet<u32>,
161 errors: &mut Vec<VerifyError>,
162) {
163 use rustc_hash::FxHashSet;
164
165 let mut produced: FxHashSet<u32> = FxHashSet::default();
167 for (i, op) in body.ops.iter().enumerate() {
168 for r in op.result_ids() {
169 if !produced.insert(r) {
170 errors.push(VerifyError {
171 body_path: path.clone(),
172 op_index: i,
173 kind: VerifyErrorKind::DuplicateResultId(r),
174 });
175 }
176 }
177 }
178
179 let mut produced_so_far: FxHashSet<u32> = FxHashSet::default();
181 let child_results: Vec<FxHashSet<u32>> =
182 body.child_bodies.iter().map(collect_body_results).collect();
183 let mut completed_child_results: FxHashSet<u32> = FxHashSet::default();
184 let mut child_scopes = vec![FxHashSet::default(); body.child_bodies.len()];
185 for (i, op) in body.ops.iter().enumerate() {
186 if matches!(op.kind, KernelOpKind::Literal) {
188 if op.operands.is_empty() {
189 errors.push(VerifyError {
190 body_path: path.clone(),
191 op_index: i,
192 kind: VerifyErrorKind::LiteralOpMissingPoolOperand,
193 });
194 } else {
195 let pool_idx = op.operands[0];
196 if (pool_idx as usize) >= body.literals.len() {
197 errors.push(VerifyError {
198 body_path: path.clone(),
199 op_index: i,
200 kind: VerifyErrorKind::LiteralPoolOutOfRange {
201 operand_pos: 0,
202 pool_idx,
203 pool_size: body.literals.len(),
204 },
205 });
206 }
207 }
208 }
209
210 for (pos, &val) in op.operands.iter().enumerate() {
212 let cls = classify_operand(&op.kind, pos);
213 match cls {
214 OperandClass::ResultRef => {
215 if !produced_so_far.contains(&val)
216 && !produced.contains(&val)
217 && !inherited_results.contains(&val)
218 && !completed_child_results.contains(&val)
219 {
220 errors.push(VerifyError {
221 body_path: path.clone(),
222 op_index: i,
223 kind: VerifyErrorKind::DanglingResultRef {
224 operand_pos: pos,
225 ref_id: val,
226 },
227 });
228 }
229 }
230 OperandClass::ChildBodyIdx => {
231 if (val as usize) >= body.child_bodies.len() {
232 errors.push(VerifyError {
233 body_path: path.clone(),
234 op_index: i,
235 kind: VerifyErrorKind::ChildBodyIndexOutOfRange {
236 operand_pos: pos,
237 body_idx: val,
238 child_count: body.child_bodies.len(),
239 },
240 });
241 } else {
242 let child_scope = &mut child_scopes[val as usize];
243 child_scope.extend(inherited_results.iter().copied());
244 child_scope.extend(produced_so_far.iter().copied());
245 child_scope.extend(completed_child_results.iter().copied());
246 }
247 }
248 OperandClass::LiteralPoolIdx => {
249 if (val as usize) >= body.literals.len() {
250 errors.push(VerifyError {
251 body_path: path.clone(),
252 op_index: i,
253 kind: VerifyErrorKind::LiteralPoolOutOfRange {
254 operand_pos: pos,
255 pool_idx: val,
256 pool_size: body.literals.len(),
257 },
258 });
259 }
260 }
261 OperandClass::Other => {}
262 }
263 }
264
265 let min_required = min_operand_count(&op.kind);
268 if op.operands.len() < min_required {
269 errors.push(VerifyError {
270 body_path: path.clone(),
271 op_index: i,
272 kind: VerifyErrorKind::OperandCountTooShort {
273 expected_min: min_required,
274 got: op.operands.len(),
275 },
276 });
277 }
278
279 for r in op.result_ids() {
280 produced_so_far.insert(r);
281 }
282 for child_idx in child_body_operands(op) {
283 if let Some(results) = child_results.get(child_idx as usize) {
284 completed_child_results.extend(results.iter().copied());
285 }
286 }
287 }
288
289 for (idx, child) in body.child_bodies.iter().enumerate() {
291 path.push(idx);
292 verify_body(child, path, &child_scopes[idx], errors);
293 path.pop();
294 }
295}
296
297fn collect_body_results(body: &KernelBody) -> FxHashSet<u32> {
298 let mut results = FxHashSet::default();
299 for op in &body.ops {
300 for result in op.result_ids() {
301 results.insert(result);
302 }
303 }
304 for child in &body.child_bodies {
305 results.extend(collect_body_results(child));
306 }
307 results
308}
309
310fn child_body_operands(op: &crate::KernelOp) -> impl Iterator<Item = u32> + '_ {
311 op.operands
312 .iter()
313 .enumerate()
314 .filter_map(|(pos, &operand)| {
315 (classify_operand(&op.kind, pos) == OperandClass::ChildBodyIdx).then_some(operand)
316 })
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
320pub enum OperandClass {
321 ResultRef,
322 ChildBodyIdx,
323 LiteralPoolIdx,
324 Other,
326}
327
328pub fn classify_operand(kind: &KernelOpKind, pos: usize) -> OperandClass {
329 use KernelOpKind::*;
330 match kind {
331 Literal => {
332 if pos == 0 {
333 OperandClass::LiteralPoolIdx
334 } else {
335 OperandClass::Other
336 }
337 }
338 LocalInvocationId | GlobalInvocationId | WorkgroupId => OperandClass::Other,
339 SubgroupLocalId | SubgroupSize => OperandClass::Other,
340 LoopIndex { .. } => OperandClass::Other,
341 BufferLength => OperandClass::Other,
342 LoadGlobal | LoadShared | LoadConstant => {
343 if pos == 0 {
344 OperandClass::Other
345 } else {
346 OperandClass::ResultRef
347 }
348 }
349 StoreGlobal | StoreShared => {
350 if pos == 0 {
351 OperandClass::Other
352 } else {
353 OperandClass::ResultRef
354 }
355 }
356 Copy | BinOpKind(_) | UnOpKind(_) | Fma | MatrixMma { .. } | Select | Cast { .. } => {
357 OperandClass::ResultRef
358 }
359 Atomic { .. } => {
360 if pos == 0 {
361 OperandClass::Other
362 } else {
363 OperandClass::ResultRef
364 }
365 }
366 SubgroupBallot | SubgroupShuffle | SubgroupBroadcast | SubgroupReduce { .. } => OperandClass::ResultRef,
367 StructuredIfThen => {
368 if pos == 0 {
369 OperandClass::ResultRef
370 } else if pos == 1 {
371 OperandClass::ChildBodyIdx
372 } else {
373 OperandClass::Other
374 }
375 }
376 StructuredIfThenElse => {
377 if pos == 0 {
378 OperandClass::ResultRef
379 } else if pos == 1 || pos == 2 {
380 OperandClass::ChildBodyIdx
381 } else {
382 OperandClass::Other
383 }
384 }
385 StructuredForLoop { .. } => {
386 if pos == 0 || pos == 1 {
387 OperandClass::ResultRef
388 } else if pos == 2 {
389 OperandClass::ChildBodyIdx
390 } else {
391 OperandClass::Other
392 }
393 }
394 StructuredBlock => {
395 if pos == 0 {
396 OperandClass::ChildBodyIdx
397 } else {
398 OperandClass::Other
399 }
400 }
401 Region { .. } => {
402 if pos == 0 {
403 OperandClass::ChildBodyIdx
404 } else {
405 OperandClass::Other
406 }
407 }
408 Return | Barrier { .. } => OperandClass::Other,
409 AsyncLoad { .. } | AsyncStore { .. } => {
410 if pos < 2 {
411 OperandClass::Other
412 } else {
413 OperandClass::ResultRef
414 }
415 }
416 AsyncWait { .. } => OperandClass::Other,
417 Trap { .. } => {
418 if pos == 0 {
419 OperandClass::ResultRef
420 } else {
421 OperandClass::Other
422 }
423 }
424 Resume { .. } => OperandClass::Other,
425 IndirectDispatch { .. } => OperandClass::Other,
426 Call { .. } => OperandClass::ResultRef,
427 OpaqueExpr(..) | OpaqueNode(..) => OperandClass::ResultRef,
428 LoopCarrierInit { .. } | LoopCarrier { .. } | LoopCarrierEnd { .. } => {
429 OperandClass::ResultRef
430 }
431 }
432}
433
434fn min_operand_count(kind: &KernelOpKind) -> usize {
435 use KernelOpKind::*;
436 match kind {
437 Literal => 1,
438 Copy => 1,
439 LocalInvocationId | GlobalInvocationId | WorkgroupId => 0,
440 SubgroupLocalId | SubgroupSize => 0,
441 LoopIndex { .. } => 0,
442 BufferLength => 1,
443 LoadGlobal | LoadShared | LoadConstant => 2,
444 StoreGlobal | StoreShared => 3,
445 BinOpKind(_) => 2,
446 UnOpKind(_) | Cast { .. } => 1,
447 Fma => 3,
448 MatrixMma { .. } => 10,
449 Select => 3,
450 Atomic { .. } => 2,
451 SubgroupBallot | SubgroupShuffle | SubgroupBroadcast | SubgroupReduce { .. } => 1,
452 StructuredIfThen => 2,
453 StructuredIfThenElse => 3,
454 StructuredForLoop { .. } => 3,
455 StructuredBlock => 1,
456 Region { .. } => 1,
457 Return => 0,
458 Barrier { .. } => 0,
459 AsyncLoad { .. } | AsyncStore { .. } => 2,
460 AsyncWait { .. } => 0,
461 Trap { .. } => 1,
462 Resume { .. } => 0,
463 IndirectDispatch { .. } => 0,
464 Call { .. } => 0,
465 OpaqueExpr(..) | OpaqueNode(..) => 0,
466 LoopCarrier { .. } => 0,
467 LoopCarrierInit { .. } | LoopCarrierEnd { .. } => 1,
468 }
469}
470
471#[cfg(test)]
472
473mod tests {
474 use super::*;
475 use crate::{
476 BindingLayout, Dispatch, KernelBody, KernelDescriptor, KernelOp, KernelOpKind, LiteralValue,
477 };
478 use vyre_foundation::ir::BinOp;
479
480 fn empty_desc(ops: Vec<KernelOp>, literals: Vec<LiteralValue>) -> KernelDescriptor {
481 KernelDescriptor {
482 id: "k".into(),
483 bindings: BindingLayout { slots: vec![] },
484 dispatch: Dispatch::new(1, 1, 1),
485 body: KernelBody {
486 ops,
487 child_bodies: vec![],
488 literals,
489 },
490 }
491 }
492
493 #[test]
494 fn empty_kernel_verifies() {
495 assert!(matches!(verify(&empty_desc(vec![], vec![])), Ok(_)));
496 }
497
498 #[test]
499 fn well_formed_kernel_verifies() {
500 let desc = empty_desc(
501 vec![
502 KernelOp {
503 kind: KernelOpKind::Literal,
504 operands: vec![0],
505 result: Some(0),
506 },
507 KernelOp {
508 kind: KernelOpKind::Literal,
509 operands: vec![1],
510 result: Some(1),
511 },
512 KernelOp {
513 kind: KernelOpKind::BinOpKind(BinOp::Add),
514 operands: vec![0, 1],
515 result: Some(2),
516 },
517 ],
518 vec![LiteralValue::U32(3), LiteralValue::U32(4)],
519 );
520 assert_eq!(verify(&desc), Ok(()));
521 }
522
523 #[test]
524 fn duplicate_result_id_detected() {
525 let desc = empty_desc(
526 vec![
527 KernelOp {
528 kind: KernelOpKind::Literal,
529 operands: vec![0],
530 result: Some(0),
531 },
532 KernelOp {
533 kind: KernelOpKind::Literal,
534 operands: vec![0],
535 result: Some(0),
536 }, ],
538 vec![LiteralValue::U32(1)],
539 );
540 let r = verify(&desc);
541 assert!(r.is_err());
542 let errs = r.unwrap_err();
543 assert!(errs
544 .iter()
545 .any(|e| matches!(e.kind, VerifyErrorKind::DuplicateResultId(0))));
546 }
547
548 #[test]
549 fn dangling_result_ref_detected() {
550 let desc = empty_desc(
551 vec![
552 KernelOp {
553 kind: KernelOpKind::Literal,
554 operands: vec![0],
555 result: Some(0),
556 },
557 KernelOp {
558 kind: KernelOpKind::BinOpKind(BinOp::Add),
559 operands: vec![0, 99], result: Some(1),
561 },
562 ],
563 vec![LiteralValue::U32(1)],
564 );
565 let r = verify(&desc);
566 let errs = r.unwrap_err();
567 assert!(errs.iter().any(|e| matches!(
568 e.kind,
569 VerifyErrorKind::DanglingResultRef { ref_id: 99, .. }
570 )));
571 }
572
573 #[test]
574 fn literal_pool_out_of_range_detected() {
575 let desc = empty_desc(
576 vec![KernelOp {
577 kind: KernelOpKind::Literal,
578 operands: vec![5], result: Some(0),
580 }],
581 vec![LiteralValue::U32(1)],
582 );
583 let r = verify(&desc);
584 let errs = r.unwrap_err();
585 assert!(errs.iter().any(|e| matches!(
586 e.kind,
587 VerifyErrorKind::LiteralPoolOutOfRange {
588 pool_idx: 5,
589 pool_size: 1,
590 ..
591 }
592 )));
593 }
594
595 #[test]
596 fn child_body_index_out_of_range_detected() {
597 let desc = KernelDescriptor {
598 id: "k".into(),
599 bindings: BindingLayout { slots: vec![] },
600 dispatch: Dispatch::new(1, 1, 1),
601 body: KernelBody {
602 ops: vec![
603 KernelOp {
604 kind: KernelOpKind::Literal,
605 operands: vec![0],
606 result: Some(0),
607 },
608 KernelOp {
609 kind: KernelOpKind::StructuredIfThen,
610 operands: vec![0, 7], result: None,
612 },
613 ],
614 child_bodies: vec![],
615 literals: vec![LiteralValue::U32(1)],
616 },
617 };
618 let r = verify(&desc);
619 let errs = r.unwrap_err();
620 assert!(errs.iter().any(|e| matches!(
621 e.kind,
622 VerifyErrorKind::ChildBodyIndexOutOfRange {
623 body_idx: 7,
624 child_count: 0,
625 ..
626 }
627 )));
628 }
629
630 #[test]
631 fn literal_op_with_no_operands_detected() {
632 let desc = empty_desc(
633 vec![KernelOp {
634 kind: KernelOpKind::Literal,
635 operands: vec![],
636 result: Some(0),
637 }],
638 vec![LiteralValue::U32(1)],
639 );
640 let r = verify(&desc);
641 let errs = r.unwrap_err();
642 assert!(errs
643 .iter()
644 .any(|e| matches!(e.kind, VerifyErrorKind::LiteralOpMissingPoolOperand)));
645 }
646
647 #[test]
648 fn operand_count_too_short_detected() {
649 let desc = empty_desc(
650 vec![
651 KernelOp {
652 kind: KernelOpKind::Literal,
653 operands: vec![0],
654 result: Some(0),
655 },
656 KernelOp {
657 kind: KernelOpKind::BinOpKind(BinOp::Add),
658 operands: vec![0], result: Some(1),
660 },
661 ],
662 vec![LiteralValue::U32(1)],
663 );
664 let r = verify(&desc);
665 let errs = r.unwrap_err();
666 assert!(errs.iter().any(|e| matches!(
667 e.kind,
668 VerifyErrorKind::OperandCountTooShort {
669 expected_min: 2,
670 got: 1
671 }
672 )));
673 }
674
675 #[test]
676 fn errors_are_collected_not_short_circuited() {
677 let desc = empty_desc(
679 vec![
680 KernelOp {
681 kind: KernelOpKind::Literal,
682 operands: vec![99],
683 result: Some(0),
684 }, KernelOp {
686 kind: KernelOpKind::Literal,
687 operands: vec![0],
688 result: Some(0),
689 }, KernelOp {
691 kind: KernelOpKind::BinOpKind(BinOp::Add),
692 operands: vec![100, 200], result: Some(1),
694 },
695 ],
696 vec![LiteralValue::U32(1)],
697 );
698 let r = verify(&desc);
699 let errs = r.unwrap_err();
700 assert!(errs.len() >= 3);
701 }
702
703 #[test]
704 fn child_body_violations_recurse() {
705 let desc = KernelDescriptor {
706 id: "k".into(),
707 bindings: BindingLayout { slots: vec![] },
708 dispatch: Dispatch::new(1, 1, 1),
709 body: KernelBody {
710 ops: vec![],
711 child_bodies: vec![KernelBody {
712 ops: vec![KernelOp {
713 kind: KernelOpKind::Literal,
714 operands: vec![99],
715 result: Some(0),
716 }],
717 child_bodies: vec![],
718 literals: vec![LiteralValue::U32(1)],
719 }],
720 literals: vec![],
721 },
722 };
723 let r = verify(&desc);
724 let errs = r.unwrap_err();
725 assert!(errs.iter().any(|e| e.body_path == vec![0]));
726 }
727
728 #[test]
729 fn child_body_may_capture_parent_result_available_before_control_op() {
730 let child = KernelBody {
731 ops: vec![KernelOp {
732 kind: KernelOpKind::BinOpKind(BinOp::Add),
733 operands: vec![0, 0],
734 result: Some(1),
735 }],
736 child_bodies: vec![],
737 literals: vec![],
738 };
739 let desc = KernelDescriptor {
740 id: "captures".into(),
741 bindings: BindingLayout { slots: vec![] },
742 dispatch: Dispatch::new(1, 1, 1),
743 body: KernelBody {
744 ops: vec![
745 KernelOp {
746 kind: KernelOpKind::Literal,
747 operands: vec![0],
748 result: Some(0),
749 },
750 KernelOp {
751 kind: KernelOpKind::StructuredBlock,
752 operands: vec![0],
753 result: None,
754 },
755 ],
756 child_bodies: vec![child],
757 literals: vec![LiteralValue::U32(7)],
758 },
759 };
760
761 assert_eq!(verify(&desc), Ok(()));
762 }
763
764 #[test]
765 fn child_body_cannot_capture_parent_result_declared_after_control_op() {
766 let child = KernelBody {
767 ops: vec![KernelOp {
768 kind: KernelOpKind::BinOpKind(BinOp::Add),
769 operands: vec![1, 1],
770 result: Some(2),
771 }],
772 child_bodies: vec![],
773 literals: vec![],
774 };
775 let desc = KernelDescriptor {
776 id: "future_capture".into(),
777 bindings: BindingLayout { slots: vec![] },
778 dispatch: Dispatch::new(1, 1, 1),
779 body: KernelBody {
780 ops: vec![
781 KernelOp {
782 kind: KernelOpKind::Literal,
783 operands: vec![0],
784 result: Some(0),
785 },
786 KernelOp {
787 kind: KernelOpKind::StructuredBlock,
788 operands: vec![0],
789 result: None,
790 },
791 KernelOp {
792 kind: KernelOpKind::Literal,
793 operands: vec![1],
794 result: Some(1),
795 },
796 ],
797 child_bodies: vec![child],
798 literals: vec![LiteralValue::U32(7), LiteralValue::U32(9)],
799 },
800 };
801
802 let errors = verify(&desc).expect_err("future child capture must fail");
803 assert!(errors.iter().any(|error| {
804 error.body_path == vec![0]
805 && matches!(
806 error.kind,
807 VerifyErrorKind::DanglingResultRef { ref_id: 1, .. }
808 )
809 }));
810 }
811
812 #[test]
813 fn parent_body_may_read_result_assigned_by_completed_child_body() {
814 let child = KernelBody {
815 ops: vec![KernelOp {
816 kind: KernelOpKind::BinOpKind(BinOp::Add),
817 operands: vec![0, 0],
818 result: Some(1),
819 }],
820 child_bodies: vec![],
821 literals: vec![],
822 };
823 let desc = KernelDescriptor {
824 id: "loop_carried".into(),
825 bindings: BindingLayout { slots: vec![] },
826 dispatch: Dispatch::new(1, 1, 1),
827 body: KernelBody {
828 ops: vec![
829 KernelOp {
830 kind: KernelOpKind::Literal,
831 operands: vec![0],
832 result: Some(0),
833 },
834 KernelOp {
835 kind: KernelOpKind::StructuredBlock,
836 operands: vec![0],
837 result: None,
838 },
839 KernelOp {
840 kind: KernelOpKind::BinOpKind(BinOp::Mul),
841 operands: vec![1, 0],
842 result: Some(2),
843 },
844 ],
845 child_bodies: vec![child],
846 literals: vec![LiteralValue::U32(7)],
847 },
848 };
849
850 assert_eq!(verify(&desc), Ok(()));
851 }
852
853 #[test]
854 fn run_all_output_verifies() {
855 let desc = empty_desc(
859 vec![
860 KernelOp {
861 kind: KernelOpKind::Literal,
862 operands: vec![0],
863 result: Some(0),
864 },
865 KernelOp {
866 kind: KernelOpKind::Literal,
867 operands: vec![1],
868 result: Some(1),
869 },
870 KernelOp {
871 kind: KernelOpKind::BinOpKind(BinOp::Add),
872 operands: vec![0, 1],
873 result: Some(2),
874 },
875 KernelOp {
876 kind: KernelOpKind::BinOpKind(BinOp::Mul),
877 operands: vec![1, 0],
878 result: Some(3),
879 },
880 ],
881 vec![LiteralValue::U32(0), LiteralValue::U32(99)],
882 );
883 let optimized = crate::rewrites::run_all(&desc);
884 assert_eq!(verify(&optimized), Ok(()));
885 }
886
887 #[test]
888 fn dispatch_zero_x_dim_detected() {
889 let desc = KernelDescriptor {
890 id: "k".into(),
891 bindings: BindingLayout { slots: vec![] },
892 dispatch: Dispatch::new(0, 1, 1),
893 body: KernelBody {
894 ops: vec![],
895 child_bodies: vec![],
896 literals: vec![],
897 },
898 };
899 let r = verify(&desc);
900 let errs = r.unwrap_err();
901 assert!(errs
902 .iter()
903 .any(|e| matches!(e.kind, VerifyErrorKind::DispatchZeroDim { axis: 0 })));
904 }
905
906 #[test]
907 fn dispatch_zero_z_dim_detected() {
908 let desc = KernelDescriptor {
909 id: "k".into(),
910 bindings: BindingLayout { slots: vec![] },
911 dispatch: Dispatch::new(64, 1, 0),
912 body: KernelBody {
913 ops: vec![],
914 child_bodies: vec![],
915 literals: vec![],
916 },
917 };
918 let r = verify(&desc);
919 let errs = r.unwrap_err();
920 assert!(errs
921 .iter()
922 .any(|e| matches!(e.kind, VerifyErrorKind::DispatchZeroDim { axis: 2 })));
923 }
924
925 #[test]
926 fn duplicate_binding_slot_detected() {
927 use crate::{BindingSlot, BindingVisibility, MemoryClass};
928 use vyre_foundation::ir::DataType;
929 let dup = BindingSlot {
930 slot: 5,
931 element_type: DataType::U32,
932 element_count: None,
933 memory_class: MemoryClass::Global,
934 visibility: BindingVisibility::ReadWrite,
935 name: "a".into(),
936 };
937 let mut second = dup.clone();
938 second.name = "b".into();
939 let desc = KernelDescriptor {
940 id: "k".into(),
941 bindings: BindingLayout {
942 slots: vec![dup, second],
943 },
944 dispatch: Dispatch::new(64, 1, 1),
945 body: KernelBody {
946 ops: vec![],
947 child_bodies: vec![],
948 literals: vec![],
949 },
950 };
951 let r = verify(&desc);
952 let errs = r.unwrap_err();
953 assert!(errs
954 .iter()
955 .any(|e| matches!(e.kind, VerifyErrorKind::DuplicateBindingSlotId { slot: 5 })));
956 }
957
958 #[test]
959 fn dispatch_normal_no_error() {
960 let desc = KernelDescriptor {
961 id: "k".into(),
962 bindings: BindingLayout { slots: vec![] },
963 dispatch: Dispatch::new(64, 1, 1),
964 body: KernelBody {
965 ops: vec![],
966 child_bodies: vec![],
967 literals: vec![],
968 },
969 };
970 assert_eq!(verify(&desc), Ok(()));
971 }
972
973 #[test]
974 fn host_binding_in_workgroup_range_is_rejected() {
975 use crate::{BindingSlot, BindingVisibility, MemoryClass};
976 use vyre_foundation::ir::DataType;
977 let bad = BindingSlot {
978 slot: crate::lower::WORKGROUP_SLOT_BASE + 7,
979 element_type: DataType::U32,
980 element_count: None,
981 memory_class: MemoryClass::Global,
982 visibility: BindingVisibility::ReadWrite,
983 name: "host_in_high_range".into(),
984 };
985 let desc = KernelDescriptor {
986 id: "k".into(),
987 bindings: BindingLayout { slots: vec![bad] },
988 dispatch: Dispatch::new(64, 1, 1),
989 body: KernelBody {
990 ops: vec![],
991 child_bodies: vec![],
992 literals: vec![],
993 },
994 };
995 let errs = verify(&desc).unwrap_err();
996 assert!(errs
997 .iter()
998 .any(|e| matches!(e.kind, VerifyErrorKind::HostBindingInWorkgroupRange { .. })));
999 }
1000
1001 #[test]
1002 fn workgroup_binding_in_host_range_is_rejected() {
1003 use crate::{BindingSlot, BindingVisibility, MemoryClass};
1004 use vyre_foundation::ir::DataType;
1005 let bad = BindingSlot {
1006 slot: 5,
1007 element_type: DataType::U32,
1008 element_count: Some(64),
1009 memory_class: MemoryClass::Shared,
1010 visibility: BindingVisibility::ReadWrite,
1011 name: "shared_in_low_range".into(),
1012 };
1013 let desc = KernelDescriptor {
1014 id: "k".into(),
1015 bindings: BindingLayout { slots: vec![bad] },
1016 dispatch: Dispatch::new(64, 1, 1),
1017 body: KernelBody {
1018 ops: vec![],
1019 child_bodies: vec![],
1020 literals: vec![],
1021 },
1022 };
1023 let errs = verify(&desc).unwrap_err();
1024 assert!(errs
1025 .iter()
1026 .any(|e| matches!(e.kind, VerifyErrorKind::WorkgroupBindingInHostRange { .. })));
1027 }
1028}