1use alloc::vec::Vec;
4use core::fmt;
5
6use crate::entity::{Block, Value};
7use crate::function::Function;
8use crate::inst::{BinOp, Inst, Terminator, UnOp};
9use crate::ty::Type;
10
11#[derive(Clone, PartialEq, Debug)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[non_exhaustive]
20pub enum ValidationError {
21 MissingTerminator {
24 block: Block,
26 },
27 BlockOutOfRange {
31 block: Block,
33 },
34 ValueOutOfRange {
38 value: Value,
40 },
41 EntryBranchTarget {
44 block: Block,
46 },
47 ArgCountMismatch {
50 block: Block,
52 expected: usize,
54 found: usize,
56 },
57 TypeMismatch {
62 value: Value,
64 expected: Type,
66 found: Type,
68 },
69 NotNumeric {
73 value: Value,
75 found: Type,
77 },
78 ReturnValueExpected {
81 expected: Type,
83 },
84 UseBeforeDef {
88 value: Value,
90 block: Block,
92 },
93}
94
95impl fmt::Display for ValidationError {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 match self {
98 ValidationError::MissingTerminator { block } => {
99 write!(f, "block {block} has no terminator")
100 }
101 ValidationError::BlockOutOfRange { block } => {
102 write!(f, "terminator targets nonexistent block {block}")
103 }
104 ValidationError::ValueOutOfRange { value } => {
105 write!(f, "reference to nonexistent value {value}")
106 }
107 ValidationError::EntryBranchTarget { block } => {
108 write!(f, "block {block} branches to the entry block")
109 }
110 ValidationError::ArgCountMismatch {
111 block,
112 expected,
113 found,
114 } => write!(
115 f,
116 "branch to block {block} passes {found} argument(s) but it has {expected} parameter(s)"
117 ),
118 ValidationError::TypeMismatch {
119 value,
120 expected,
121 found,
122 } => write!(
123 f,
124 "value {value} has type {found} but {expected} was required"
125 ),
126 ValidationError::NotNumeric { value, found } => {
127 write!(
128 f,
129 "value {value} has type {found} but a numeric type was required"
130 )
131 }
132 ValidationError::ReturnValueExpected { expected } => {
133 write!(f, "return with no value in a function returning {expected}")
134 }
135 ValidationError::UseBeforeDef { value, block } => {
136 write!(
137 f,
138 "value {value} is used in block {block} before it is defined"
139 )
140 }
141 }
142 }
143}
144
145impl core::error::Error for ValidationError {}
146
147pub(crate) fn validate(func: &Function) -> Result<(), ValidationError> {
152 let block_count = func.block_count();
153
154 let mut succs: Vec<Vec<Block>> = Vec::with_capacity(block_count);
156 for block in func.blocks() {
157 let term = func
158 .terminator(block)
159 .ok_or(ValidationError::MissingTerminator { block })?;
160 check_terminator(func, block, term, block_count)?;
161 check_block_insts(func, block)?;
162 succs.push(successors(term));
163 }
164
165 check_dominance(func, &succs)
169}
170
171fn check_terminator(
175 func: &Function,
176 block: Block,
177 term: &Terminator,
178 block_count: usize,
179) -> Result<(), ValidationError> {
180 match term {
181 Terminator::Return(None) => {
182 if func.ret() != Type::Unit {
183 return Err(ValidationError::ReturnValueExpected {
184 expected: func.ret(),
185 });
186 }
187 }
188 Terminator::Return(Some(value)) => {
189 let found = value_type(func, *value)?;
190 if found != func.ret() {
191 return Err(ValidationError::TypeMismatch {
192 value: *value,
193 expected: func.ret(),
194 found,
195 });
196 }
197 }
198 Terminator::Jump(target, args) => {
199 check_edge(func, *target, args, block, block_count)?;
200 }
201 Terminator::Branch {
202 cond,
203 then_block,
204 then_args,
205 else_block,
206 else_args,
207 } => {
208 let cond_ty = value_type(func, *cond)?;
209 if cond_ty != Type::Bool {
210 return Err(ValidationError::TypeMismatch {
211 value: *cond,
212 expected: Type::Bool,
213 found: cond_ty,
214 });
215 }
216 check_edge(func, *then_block, then_args, block, block_count)?;
217 check_edge(func, *else_block, else_args, block, block_count)?;
218 }
219 }
220 Ok(())
221}
222
223fn check_edge(
226 func: &Function,
227 target: Block,
228 args: &[Value],
229 from: Block,
230 block_count: usize,
231) -> Result<(), ValidationError> {
232 if target.index() >= block_count {
233 return Err(ValidationError::BlockOutOfRange { block: target });
234 }
235 if target == func.entry() {
236 return Err(ValidationError::EntryBranchTarget { block: from });
237 }
238 let params = func.block_params(target);
239 if args.len() != params.len() {
240 return Err(ValidationError::ArgCountMismatch {
241 block: target,
242 expected: params.len(),
243 found: args.len(),
244 });
245 }
246 for (&arg, ¶m) in args.iter().zip(params.iter()) {
247 let arg_ty = value_type(func, arg)?;
248 let param_ty = value_type(func, param)?;
249 if arg_ty != param_ty {
250 return Err(ValidationError::TypeMismatch {
251 value: arg,
252 expected: param_ty,
253 found: arg_ty,
254 });
255 }
256 }
257 Ok(())
258}
259
260fn check_block_insts(func: &Function, block: Block) -> Result<(), ValidationError> {
262 for &value in func.insts(block) {
263 if let Some(inst) = func.inst(value) {
264 check_inst(func, inst)?;
265 }
266 }
267 Ok(())
268}
269
270fn check_inst(func: &Function, inst: &Inst) -> Result<(), ValidationError> {
272 match inst {
273 Inst::Iconst(_) | Inst::Fconst(_) | Inst::Bconst(_) => Ok(()),
274 Inst::Bin(op, lhs, rhs) => check_bin(func, *op, *lhs, *rhs),
275 Inst::Un(op, operand) => check_un(func, *op, *operand),
276 }
277}
278
279fn check_bin(func: &Function, op: BinOp, lhs: Value, rhs: Value) -> Result<(), ValidationError> {
282 let lhs_ty = value_type(func, lhs)?;
283 let rhs_ty = value_type(func, rhs)?;
284 if lhs_ty != rhs_ty {
285 return Err(ValidationError::TypeMismatch {
286 value: rhs,
287 expected: lhs_ty,
288 found: rhs_ty,
289 });
290 }
291 if op.is_logical() {
292 if lhs_ty != Type::Bool {
293 return Err(ValidationError::TypeMismatch {
294 value: lhs,
295 expected: Type::Bool,
296 found: lhs_ty,
297 });
298 }
299 } else if requires_numeric(op) && !lhs_ty.is_numeric() {
300 return Err(ValidationError::NotNumeric {
301 value: lhs,
302 found: lhs_ty,
303 });
304 }
305 Ok(())
306}
307
308fn requires_numeric(op: BinOp) -> bool {
311 matches!(
312 op,
313 BinOp::Add
314 | BinOp::Sub
315 | BinOp::Mul
316 | BinOp::Div
317 | BinOp::Lt
318 | BinOp::Le
319 | BinOp::Gt
320 | BinOp::Ge
321 )
322}
323
324fn check_un(func: &Function, op: UnOp, operand: Value) -> Result<(), ValidationError> {
326 let ty = value_type(func, operand)?;
327 match op {
328 UnOp::Neg if !ty.is_numeric() => Err(ValidationError::NotNumeric {
329 value: operand,
330 found: ty,
331 }),
332 UnOp::Not if ty != Type::Bool => Err(ValidationError::TypeMismatch {
333 value: operand,
334 expected: Type::Bool,
335 found: ty,
336 }),
337 _ => Ok(()),
338 }
339}
340
341fn value_type(func: &Function, value: Value) -> Result<Type, ValidationError> {
344 func.value_type(value)
345 .ok_or(ValidationError::ValueOutOfRange { value })
346}
347
348fn successors(term: &Terminator) -> Vec<Block> {
350 let mut out = Vec::new();
351 term.each_successor(|b| out.push(b));
352 out
353}
354
355fn check_dominance(func: &Function, succs: &[Vec<Block>]) -> Result<(), ValidationError> {
358 let n = succs.len();
359 let entry = func.entry().index();
360 let idom = compute_idoms(entry, n, succs);
361
362 for bi in 0..n {
363 if idom[bi].is_none() {
365 continue;
366 }
367 let block = Block::from_raw(bi as u32);
368
369 let mut available = alloc::vec![false; func.value_count()];
372 for dom in strict_dominators(bi, entry, &idom) {
373 mark_defs(func, Block::from_raw(dom as u32), &mut available);
374 }
375 for ¶m in func.block_params(block) {
376 set_available(&mut available, param);
377 }
378
379 for &value in func.insts(block) {
382 if let Some(inst) = func.inst(value) {
383 check_operands_available(inst, &available, block)?;
384 }
385 set_available(&mut available, value);
386 }
387 if let Some(term) = func.terminator(block) {
388 check_terminator_operands_available(term, &available, block)?;
389 }
390 }
391 Ok(())
392}
393
394fn mark_defs(func: &Function, block: Block, available: &mut [bool]) {
397 for ¶m in func.block_params(block) {
398 set_available(available, param);
399 }
400 for &value in func.insts(block) {
401 set_available(available, value);
402 }
403}
404
405fn set_available(available: &mut [bool], value: Value) {
406 if let Some(slot) = available.get_mut(value.index()) {
407 *slot = true;
408 }
409}
410
411fn is_available(available: &[bool], value: Value) -> bool {
412 available.get(value.index()).copied().unwrap_or(false)
413}
414
415fn check_operands_available(
417 inst: &Inst,
418 available: &[bool],
419 block: Block,
420) -> Result<(), ValidationError> {
421 match inst {
422 Inst::Iconst(_) | Inst::Fconst(_) | Inst::Bconst(_) => Ok(()),
423 Inst::Bin(_, lhs, rhs) => {
424 require_available(*lhs, available, block)?;
425 require_available(*rhs, available, block)
426 }
427 Inst::Un(_, operand) => require_available(*operand, available, block),
428 }
429}
430
431fn check_terminator_operands_available(
433 term: &Terminator,
434 available: &[bool],
435 block: Block,
436) -> Result<(), ValidationError> {
437 match term {
438 Terminator::Return(None) => Ok(()),
439 Terminator::Return(Some(value)) => require_available(*value, available, block),
440 Terminator::Jump(_, args) => {
441 for &arg in args {
442 require_available(arg, available, block)?;
443 }
444 Ok(())
445 }
446 Terminator::Branch {
447 cond,
448 then_args,
449 else_args,
450 ..
451 } => {
452 require_available(*cond, available, block)?;
453 for &arg in then_args.iter().chain(else_args.iter()) {
454 require_available(arg, available, block)?;
455 }
456 Ok(())
457 }
458 }
459}
460
461fn require_available(
462 value: Value,
463 available: &[bool],
464 block: Block,
465) -> Result<(), ValidationError> {
466 if is_available(available, value) {
467 Ok(())
468 } else {
469 Err(ValidationError::UseBeforeDef { value, block })
470 }
471}
472
473fn strict_dominators(bi: usize, entry: usize, idom: &[Option<usize>]) -> Vec<usize> {
476 let mut out = Vec::new();
477 let mut cur = idom[bi];
478 while let Some(d) = cur {
479 if d == bi {
480 break; }
482 out.push(d);
483 if d == entry {
484 break;
485 }
486 cur = idom[d];
487 }
488 out
489}
490
491fn compute_idoms(entry: usize, n: usize, succs: &[Vec<Block>]) -> Vec<Option<usize>> {
495 let postorder = postorder(entry, n, succs);
496 let mut po_num = alloc::vec![usize::MAX; n];
497 for (i, &b) in postorder.iter().enumerate() {
498 po_num[b] = i;
499 }
500
501 let mut preds: Vec<Vec<usize>> = alloc::vec![Vec::new(); n];
502 for (b, block_succs) in succs.iter().enumerate() {
503 for s in block_succs {
504 if let Some(slot) = preds.get_mut(s.index()) {
505 slot.push(b);
506 }
507 }
508 }
509
510 let mut idom = alloc::vec![None; n];
511 idom[entry] = Some(entry);
512
513 let rpo: Vec<usize> = postorder.iter().rev().copied().collect();
515 let mut changed = true;
516 while changed {
517 changed = false;
518 for &b in &rpo {
519 if b == entry {
520 continue;
521 }
522 let mut new_idom: Option<usize> = None;
523 for &p in &preds[b] {
524 if idom[p].is_some() {
525 new_idom = Some(match new_idom {
526 None => p,
527 Some(cur) => intersect(p, cur, &idom, &po_num, entry),
528 });
529 }
530 }
531 if idom[b] != new_idom {
532 idom[b] = new_idom;
533 changed = true;
534 }
535 }
536 }
537 idom
538}
539
540fn intersect(
543 mut a: usize,
544 mut b: usize,
545 idom: &[Option<usize>],
546 po_num: &[usize],
547 entry: usize,
548) -> usize {
549 while a != b {
550 while po_num[a] < po_num[b] {
551 a = idom[a].unwrap_or(entry);
552 }
553 while po_num[b] < po_num[a] {
554 b = idom[b].unwrap_or(entry);
555 }
556 }
557 a
558}
559
560fn postorder(entry: usize, n: usize, succs: &[Vec<Block>]) -> Vec<usize> {
563 let mut visited = alloc::vec![false; n];
564 let mut order = Vec::new();
565 let mut stack: Vec<(usize, usize)> = Vec::new();
566
567 if entry >= n {
568 return order;
569 }
570 visited[entry] = true;
571 stack.push((entry, 0));
572
573 while let Some(&(b, i)) = stack.last() {
574 if i < succs[b].len() {
575 let top = stack.len() - 1;
576 stack[top].1 += 1;
577 let s = succs[b][i].index();
578 if s < n && !visited[s] {
579 visited[s] = true;
580 stack.push((s, 0));
581 }
582 } else {
583 order.push(b);
584 stack.pop();
585 }
586 }
587 order
588}
589
590#[cfg(test)]
591#[allow(
592 clippy::expect_used,
593 reason = "tests assert on specific error variants; a wrong variant should fail loudly"
594)]
595mod tests {
596 use crate::function::{BlockData, Function};
597 use crate::{BinOp, Block, Builder, Terminator, Type, UnOp, Value};
598
599 use super::ValidationError;
600 use alloc::string::ToString;
601 use alloc::vec;
602
603 #[test]
604 fn test_valid_straight_line_function_passes() {
605 let mut b = Builder::new("add", &[Type::Int, Type::Int], Type::Int);
606 let x = b.block_params(b.entry())[0];
607 let y = b.block_params(b.entry())[1];
608 let sum = b.bin(BinOp::Add, x, y);
609 b.ret(Some(sum));
610 assert_eq!(b.finish().validate(), Ok(()));
611 }
612
613 #[test]
614 fn test_valid_diamond_with_block_params_passes() {
615 let mut b = Builder::new("max", &[Type::Int, Type::Int], Type::Int);
616 let a = b.block_params(b.entry())[0];
617 let c = b.block_params(b.entry())[1];
618 let join = b.create_block(&[Type::Int]);
619 let then_blk = b.create_block(&[]);
620 let else_blk = b.create_block(&[]);
621 let cond = b.bin(BinOp::Lt, a, c);
622 b.branch(cond, then_blk, &[], else_blk, &[]);
623 b.switch_to(then_blk);
624 b.jump(join, &[c]);
625 b.switch_to(else_blk);
626 b.jump(join, &[a]);
627 b.switch_to(join);
628 let r = b.block_params(join)[0];
629 b.ret(Some(r));
630 assert_eq!(b.finish().validate(), Ok(()));
631 }
632
633 #[test]
634 fn test_valid_loop_passes() {
635 let mut b = Builder::new("loop", &[Type::Int], Type::Unit);
637 let start = b.block_params(b.entry())[0];
638 let header = b.create_block(&[Type::Int]);
639 let body = b.create_block(&[]);
640 let exit = b.create_block(&[]);
641 b.jump(header, &[start]);
642 b.switch_to(header);
643 let i = b.block_params(header)[0];
644 let zero = b.iconst(0);
645 let more = b.bin(BinOp::Gt, i, zero);
646 b.branch(more, body, &[], exit, &[]);
647 b.switch_to(body);
648 let one = b.iconst(1);
649 let next = b.bin(BinOp::Sub, i, one);
650 b.jump(header, &[next]);
651 b.switch_to(exit);
652 b.ret(None);
653 assert_eq!(b.finish().validate(), Ok(()));
654 }
655
656 #[test]
657 fn test_missing_terminator_is_rejected() {
658 let b = Builder::new("f", &[], Type::Unit);
659 let func = b.finish();
661 assert_eq!(
662 func.validate(),
663 Err(ValidationError::MissingTerminator {
664 block: func.entry()
665 })
666 );
667 }
668
669 #[test]
670 fn test_arg_count_mismatch_is_rejected() {
671 let mut b = Builder::new("f", &[], Type::Int);
672 let exit = b.create_block(&[Type::Int]);
673 let n = b.iconst(1);
674 b.jump(exit, &[]); b.switch_to(exit);
676 let r = b.block_params(exit)[0];
677 b.ret(Some(r));
678 let _ = n;
679 assert_eq!(
680 b.finish().validate(),
681 Err(ValidationError::ArgCountMismatch {
682 block: exit,
683 expected: 1,
684 found: 0,
685 })
686 );
687 }
688
689 #[test]
690 fn test_operand_type_mismatch_is_rejected() {
691 let mut b = Builder::new("f", &[Type::Int, Type::Bool], Type::Int);
692 let x = b.block_params(b.entry())[0];
693 let flag = b.block_params(b.entry())[1];
694 let bad = b.bin(BinOp::Add, x, flag); b.ret(Some(bad));
696 assert_eq!(
697 b.finish().validate(),
698 Err(ValidationError::TypeMismatch {
699 value: flag,
700 expected: Type::Int,
701 found: Type::Bool,
702 })
703 );
704 }
705
706 #[test]
707 fn test_non_numeric_arithmetic_is_rejected() {
708 let mut b = Builder::new("f", &[Type::Bool, Type::Bool], Type::Bool);
709 let p = b.block_params(b.entry())[0];
710 let q = b.block_params(b.entry())[1];
711 let bad = b.bin(BinOp::Mul, p, q); b.ret(Some(bad));
713 assert_eq!(
714 b.finish().validate(),
715 Err(ValidationError::NotNumeric {
716 value: p,
717 found: Type::Bool,
718 })
719 );
720 }
721
722 #[test]
723 fn test_non_bool_condition_is_rejected() {
724 let mut b = Builder::new("f", &[Type::Int], Type::Unit);
725 let x = b.block_params(b.entry())[0];
726 let yes = b.create_block(&[]);
727 let no = b.create_block(&[]);
728 b.branch(x, yes, &[], no, &[]); b.switch_to(yes);
730 b.ret(None);
731 b.switch_to(no);
732 b.ret(None);
733 assert_eq!(
734 b.finish().validate(),
735 Err(ValidationError::TypeMismatch {
736 value: x,
737 expected: Type::Bool,
738 found: Type::Int,
739 })
740 );
741 }
742
743 #[test]
744 fn test_return_type_mismatch_is_rejected() {
745 let mut b = Builder::new("f", &[Type::Bool], Type::Int);
746 let flag = b.block_params(b.entry())[0];
747 b.ret(Some(flag)); assert_eq!(
749 b.finish().validate(),
750 Err(ValidationError::TypeMismatch {
751 value: flag,
752 expected: Type::Int,
753 found: Type::Bool,
754 })
755 );
756 }
757
758 #[test]
759 fn test_return_without_value_in_non_unit_function_is_rejected() {
760 let mut b = Builder::new("f", &[], Type::Int);
761 b.ret(None);
762 assert_eq!(
763 b.finish().validate(),
764 Err(ValidationError::ReturnValueExpected {
765 expected: Type::Int
766 })
767 );
768 }
769
770 #[test]
771 fn test_branch_to_entry_is_rejected() {
772 let mut b = Builder::new("f", &[], Type::Unit);
773 let entry = b.entry();
774 b.jump(entry, &[]); assert_eq!(
776 b.finish().validate(),
777 Err(ValidationError::EntryBranchTarget { block: entry })
778 );
779 }
780
781 #[test]
782 fn test_use_before_def_across_blocks_is_rejected() {
783 let mut b = Builder::new("f", &[], Type::Int);
785 let entry = b.entry();
786 let other = b.create_block(&[]);
787 b.switch_to(other);
788 let v = b.iconst(7); b.ret(Some(v));
790 b.switch_to(entry);
791 b.ret(Some(v)); assert_eq!(
793 b.finish().validate(),
794 Err(ValidationError::UseBeforeDef {
795 value: v,
796 block: entry
797 })
798 );
799 }
800
801 #[test]
802 fn test_out_of_range_value_is_rejected() {
803 let entry = Block::from_raw(0);
805 let blocks = vec![BlockData {
806 params: vec![],
807 insts: vec![],
808 term: Some(Terminator::Return(Some(Value::from_raw(5)))),
809 }];
810 let func = Function::from_parts(
811 "f".to_string(),
812 vec![],
813 Type::Int,
814 entry,
815 blocks,
816 vec![], );
818 assert_eq!(
819 func.validate(),
820 Err(ValidationError::ValueOutOfRange {
821 value: Value::from_raw(5)
822 })
823 );
824 }
825
826 #[test]
827 fn test_out_of_range_block_is_rejected() {
828 let entry = Block::from_raw(0);
830 let blocks = vec![BlockData {
831 params: vec![],
832 insts: vec![],
833 term: Some(Terminator::Jump(Block::from_raw(9), vec![])),
834 }];
835 let func = Function::from_parts("f".to_string(), vec![], Type::Unit, entry, blocks, vec![]);
836 assert_eq!(
837 func.validate(),
838 Err(ValidationError::BlockOutOfRange {
839 block: Block::from_raw(9)
840 })
841 );
842 }
843
844 #[test]
845 fn test_not_operator_requires_bool() {
846 let mut b = Builder::new("f", &[Type::Int], Type::Int);
847 let x = b.block_params(b.entry())[0];
848 let bad = b.un(UnOp::Not, x); b.ret(Some(bad));
850 assert!(matches!(
851 b.finish().validate(),
852 Err(ValidationError::TypeMismatch { .. })
853 ));
854 }
855
856 #[test]
857 fn test_error_display_is_human_readable() {
858 let e = ValidationError::MissingTerminator {
859 block: Block::from_raw(2),
860 };
861 assert_eq!(e.to_string(), "block b2 has no terminator");
862 }
863}