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 InconsistentDefinition {
100 value: Value,
102 },
103}
104
105impl fmt::Display for ValidationError {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 ValidationError::MissingTerminator { block } => {
109 write!(f, "block {block} has no terminator")
110 }
111 ValidationError::BlockOutOfRange { block } => {
112 write!(f, "terminator targets nonexistent block {block}")
113 }
114 ValidationError::ValueOutOfRange { value } => {
115 write!(f, "reference to nonexistent value {value}")
116 }
117 ValidationError::EntryBranchTarget { block } => {
118 write!(f, "block {block} branches to the entry block")
119 }
120 ValidationError::ArgCountMismatch {
121 block,
122 expected,
123 found,
124 } => write!(
125 f,
126 "branch to block {block} passes {found} argument(s) but it has {expected} parameter(s)"
127 ),
128 ValidationError::TypeMismatch {
129 value,
130 expected,
131 found,
132 } => write!(
133 f,
134 "value {value} has type {found} but {expected} was required"
135 ),
136 ValidationError::NotNumeric { value, found } => {
137 write!(
138 f,
139 "value {value} has type {found} but a numeric type was required"
140 )
141 }
142 ValidationError::ReturnValueExpected { expected } => {
143 write!(f, "return with no value in a function returning {expected}")
144 }
145 ValidationError::UseBeforeDef { value, block } => {
146 write!(
147 f,
148 "value {value} is used in block {block} before it is defined"
149 )
150 }
151 ValidationError::InconsistentDefinition { value } => {
152 write!(
153 f,
154 "value {value} is listed in a block that disagrees with its recorded definition"
155 )
156 }
157 }
158 }
159}
160
161impl core::error::Error for ValidationError {}
162
163pub(crate) fn validate(func: &Function) -> Result<(), ValidationError> {
168 let block_count = func.block_count();
169
170 if func.entry().index() >= block_count {
174 return Err(ValidationError::BlockOutOfRange {
175 block: func.entry(),
176 });
177 }
178
179 let mut succs: Vec<Vec<Block>> = Vec::with_capacity(block_count);
181 for block in func.blocks() {
182 let term = func
183 .terminator(block)
184 .ok_or(ValidationError::MissingTerminator { block })?;
185 check_block_definitions(func, block)?;
189 check_block_insts(func, block)?;
190 check_terminator(func, block, term, block_count)?;
191 succs.push(successors(term));
192 }
193
194 check_dominance(func, &succs)
198}
199
200fn check_terminator(
204 func: &Function,
205 block: Block,
206 term: &Terminator,
207 block_count: usize,
208) -> Result<(), ValidationError> {
209 match term {
210 Terminator::Return(None) => {
211 if func.ret() != Type::Unit {
212 return Err(ValidationError::ReturnValueExpected {
213 expected: func.ret(),
214 });
215 }
216 }
217 Terminator::Return(Some(value)) => {
218 let found = value_type(func, *value)?;
219 if found != func.ret() {
220 return Err(ValidationError::TypeMismatch {
221 value: *value,
222 expected: func.ret(),
223 found,
224 });
225 }
226 }
227 Terminator::Jump(target, args) => {
228 check_edge(func, *target, args, block, block_count)?;
229 }
230 Terminator::Branch {
231 cond,
232 then_block,
233 then_args,
234 else_block,
235 else_args,
236 } => {
237 let cond_ty = value_type(func, *cond)?;
238 if cond_ty != Type::Bool {
239 return Err(ValidationError::TypeMismatch {
240 value: *cond,
241 expected: Type::Bool,
242 found: cond_ty,
243 });
244 }
245 check_edge(func, *then_block, then_args, block, block_count)?;
246 check_edge(func, *else_block, else_args, block, block_count)?;
247 }
248 }
249 Ok(())
250}
251
252fn check_edge(
255 func: &Function,
256 target: Block,
257 args: &[Value],
258 from: Block,
259 block_count: usize,
260) -> Result<(), ValidationError> {
261 if target.index() >= block_count {
262 return Err(ValidationError::BlockOutOfRange { block: target });
263 }
264 if target == func.entry() {
265 return Err(ValidationError::EntryBranchTarget { block: from });
266 }
267 let params = func.block_params(target);
268 if args.len() != params.len() {
269 return Err(ValidationError::ArgCountMismatch {
270 block: target,
271 expected: params.len(),
272 found: args.len(),
273 });
274 }
275 for (&arg, ¶m) in args.iter().zip(params.iter()) {
276 let arg_ty = value_type(func, arg)?;
277 let param_ty = value_type(func, param)?;
278 if arg_ty != param_ty {
279 return Err(ValidationError::TypeMismatch {
280 value: arg,
281 expected: param_ty,
282 found: arg_ty,
283 });
284 }
285 }
286 Ok(())
287}
288
289fn check_block_definitions(func: &Function, block: Block) -> Result<(), ValidationError> {
294 for &value in func.block_params(block) {
295 check_definition(func, value, block, DefKind::Param)?;
296 }
297 for &value in func.insts(block) {
298 check_definition(func, value, block, DefKind::Inst)?;
299 }
300 Ok(())
301}
302
303#[derive(PartialEq)]
305enum DefKind {
306 Param,
307 Inst,
308}
309
310fn check_definition(
314 func: &Function,
315 value: Value,
316 block: Block,
317 kind: DefKind,
318) -> Result<(), ValidationError> {
319 let def_block = func
320 .value_block(value)
321 .ok_or(ValidationError::ValueOutOfRange { value })?;
322 let def_kind = if func.inst(value).is_some() {
323 DefKind::Inst
324 } else {
325 DefKind::Param
326 };
327 if def_block != block || def_kind != kind {
328 return Err(ValidationError::InconsistentDefinition { value });
329 }
330 Ok(())
331}
332
333fn check_block_insts(func: &Function, block: Block) -> Result<(), ValidationError> {
336 for &value in func.insts(block) {
337 if let Some(inst) = func.inst(value) {
338 check_inst(func, inst)?;
339 check_result_type(func, value, inst)?;
340 }
341 }
342 Ok(())
343}
344
345fn check_result_type(func: &Function, value: Value, inst: &Inst) -> Result<(), ValidationError> {
349 let expected = match inst {
350 Inst::Iconst(_) => Type::Int,
351 Inst::Fconst(_) => Type::Float,
352 Inst::Bconst(_) => Type::Bool,
353 Inst::Bin(op, lhs, _) => {
354 if op.is_comparison() || op.is_logical() {
355 Type::Bool
356 } else {
357 value_type(func, *lhs)?
358 }
359 }
360 Inst::Un(UnOp::Neg, operand) => value_type(func, *operand)?,
361 Inst::Un(UnOp::Not, _) => Type::Bool,
362 };
363 let found = value_type(func, value)?;
364 if found != expected {
365 return Err(ValidationError::TypeMismatch {
366 value,
367 expected,
368 found,
369 });
370 }
371 Ok(())
372}
373
374fn check_inst(func: &Function, inst: &Inst) -> Result<(), ValidationError> {
376 match inst {
377 Inst::Iconst(_) | Inst::Fconst(_) | Inst::Bconst(_) => Ok(()),
378 Inst::Bin(op, lhs, rhs) => check_bin(func, *op, *lhs, *rhs),
379 Inst::Un(op, operand) => check_un(func, *op, *operand),
380 }
381}
382
383fn check_bin(func: &Function, op: BinOp, lhs: Value, rhs: Value) -> Result<(), ValidationError> {
386 let lhs_ty = value_type(func, lhs)?;
387 let rhs_ty = value_type(func, rhs)?;
388 if lhs_ty != rhs_ty {
389 return Err(ValidationError::TypeMismatch {
390 value: rhs,
391 expected: lhs_ty,
392 found: rhs_ty,
393 });
394 }
395 if op.is_logical() {
396 if lhs_ty != Type::Bool {
397 return Err(ValidationError::TypeMismatch {
398 value: lhs,
399 expected: Type::Bool,
400 found: lhs_ty,
401 });
402 }
403 } else if requires_numeric(op) && !lhs_ty.is_numeric() {
404 return Err(ValidationError::NotNumeric {
405 value: lhs,
406 found: lhs_ty,
407 });
408 }
409 Ok(())
410}
411
412fn requires_numeric(op: BinOp) -> bool {
415 matches!(
416 op,
417 BinOp::Add
418 | BinOp::Sub
419 | BinOp::Mul
420 | BinOp::Div
421 | BinOp::Lt
422 | BinOp::Le
423 | BinOp::Gt
424 | BinOp::Ge
425 )
426}
427
428fn check_un(func: &Function, op: UnOp, operand: Value) -> Result<(), ValidationError> {
430 let ty = value_type(func, operand)?;
431 match op {
432 UnOp::Neg if !ty.is_numeric() => Err(ValidationError::NotNumeric {
433 value: operand,
434 found: ty,
435 }),
436 UnOp::Not if ty != Type::Bool => Err(ValidationError::TypeMismatch {
437 value: operand,
438 expected: Type::Bool,
439 found: ty,
440 }),
441 _ => Ok(()),
442 }
443}
444
445fn value_type(func: &Function, value: Value) -> Result<Type, ValidationError> {
448 func.value_type(value)
449 .ok_or(ValidationError::ValueOutOfRange { value })
450}
451
452fn successors(term: &Terminator) -> Vec<Block> {
454 let mut out = Vec::new();
455 term.each_successor(|b| out.push(b));
456 out
457}
458
459enum Visit {
461 Enter(usize),
462 Exit(usize),
463}
464
465fn check_dominance(func: &Function, succs: &[Vec<Block>]) -> Result<(), ValidationError> {
475 let n = succs.len();
476 let entry = func.entry().index();
477 let idom = compute_idoms(entry, n, succs);
478
479 let mut children: Vec<Vec<usize>> = alloc::vec![Vec::new(); n];
483 for (b, dom) in idom.iter().enumerate() {
484 if let Some(parent) = *dom {
485 if b != entry {
486 children[parent].push(b);
487 }
488 }
489 }
490
491 let mut available = alloc::vec![false; func.value_count()];
492 let mut stack = alloc::vec![Visit::Enter(entry)];
493 while let Some(visit) = stack.pop() {
494 match visit {
495 Visit::Exit(b) => {
496 let block = Block::from_raw(b as u32);
497 for &value in func.block_params(block) {
498 clear_available(&mut available, value);
499 }
500 for &value in func.insts(block) {
501 clear_available(&mut available, value);
502 }
503 }
504 Visit::Enter(b) => {
505 let block = Block::from_raw(b as u32);
506 for ¶m in func.block_params(block) {
508 set_available(&mut available, param);
509 }
510 for &value in func.insts(block) {
513 if let Some(inst) = func.inst(value) {
514 check_operands_available(inst, &available, block)?;
515 }
516 set_available(&mut available, value);
517 }
518 if let Some(term) = func.terminator(block) {
519 check_terminator_operands_available(term, &available, block)?;
520 }
521 stack.push(Visit::Exit(b));
523 for &child in &children[b] {
524 stack.push(Visit::Enter(child));
525 }
526 }
527 }
528 }
529 Ok(())
530}
531
532fn set_available(available: &mut [bool], value: Value) {
533 if let Some(slot) = available.get_mut(value.index()) {
534 *slot = true;
535 }
536}
537
538fn clear_available(available: &mut [bool], value: Value) {
539 if let Some(slot) = available.get_mut(value.index()) {
540 *slot = false;
541 }
542}
543
544fn is_available(available: &[bool], value: Value) -> bool {
545 available.get(value.index()).copied().unwrap_or(false)
546}
547
548fn check_operands_available(
550 inst: &Inst,
551 available: &[bool],
552 block: Block,
553) -> Result<(), ValidationError> {
554 match inst {
555 Inst::Iconst(_) | Inst::Fconst(_) | Inst::Bconst(_) => Ok(()),
556 Inst::Bin(_, lhs, rhs) => {
557 require_available(*lhs, available, block)?;
558 require_available(*rhs, available, block)
559 }
560 Inst::Un(_, operand) => require_available(*operand, available, block),
561 }
562}
563
564fn check_terminator_operands_available(
566 term: &Terminator,
567 available: &[bool],
568 block: Block,
569) -> Result<(), ValidationError> {
570 match term {
571 Terminator::Return(None) => Ok(()),
572 Terminator::Return(Some(value)) => require_available(*value, available, block),
573 Terminator::Jump(_, args) => {
574 for &arg in args {
575 require_available(arg, available, block)?;
576 }
577 Ok(())
578 }
579 Terminator::Branch {
580 cond,
581 then_args,
582 else_args,
583 ..
584 } => {
585 require_available(*cond, available, block)?;
586 for &arg in then_args.iter().chain(else_args.iter()) {
587 require_available(arg, available, block)?;
588 }
589 Ok(())
590 }
591 }
592}
593
594fn require_available(
595 value: Value,
596 available: &[bool],
597 block: Block,
598) -> Result<(), ValidationError> {
599 if is_available(available, value) {
600 Ok(())
601 } else {
602 Err(ValidationError::UseBeforeDef { value, block })
603 }
604}
605
606fn compute_idoms(entry: usize, n: usize, succs: &[Vec<Block>]) -> Vec<Option<usize>> {
610 let postorder = postorder(entry, n, succs);
611 let mut po_num = alloc::vec![usize::MAX; n];
612 for (i, &b) in postorder.iter().enumerate() {
613 po_num[b] = i;
614 }
615
616 let mut preds: Vec<Vec<usize>> = alloc::vec![Vec::new(); n];
617 for (b, block_succs) in succs.iter().enumerate() {
618 for s in block_succs {
619 if let Some(slot) = preds.get_mut(s.index()) {
620 slot.push(b);
621 }
622 }
623 }
624
625 let mut idom = alloc::vec![None; n];
626 idom[entry] = Some(entry);
627
628 let rpo: Vec<usize> = postorder.iter().rev().copied().collect();
630 let mut changed = true;
631 while changed {
632 changed = false;
633 for &b in &rpo {
634 if b == entry {
635 continue;
636 }
637 let mut new_idom: Option<usize> = None;
638 for &p in &preds[b] {
639 if idom[p].is_some() {
640 new_idom = Some(match new_idom {
641 None => p,
642 Some(cur) => intersect(p, cur, &idom, &po_num, entry),
643 });
644 }
645 }
646 if idom[b] != new_idom {
647 idom[b] = new_idom;
648 changed = true;
649 }
650 }
651 }
652 idom
653}
654
655fn intersect(
658 mut a: usize,
659 mut b: usize,
660 idom: &[Option<usize>],
661 po_num: &[usize],
662 entry: usize,
663) -> usize {
664 while a != b {
665 while po_num[a] < po_num[b] {
666 a = idom[a].unwrap_or(entry);
667 }
668 while po_num[b] < po_num[a] {
669 b = idom[b].unwrap_or(entry);
670 }
671 }
672 a
673}
674
675fn postorder(entry: usize, n: usize, succs: &[Vec<Block>]) -> Vec<usize> {
678 let mut visited = alloc::vec![false; n];
679 let mut order = Vec::new();
680 let mut stack: Vec<(usize, usize)> = Vec::new();
681
682 if entry >= n {
683 return order;
684 }
685 visited[entry] = true;
686 stack.push((entry, 0));
687
688 while let Some(&(b, i)) = stack.last() {
689 if i < succs[b].len() {
690 let top = stack.len() - 1;
691 stack[top].1 += 1;
692 let s = succs[b][i].index();
693 if s < n && !visited[s] {
694 visited[s] = true;
695 stack.push((s, 0));
696 }
697 } else {
698 order.push(b);
699 stack.pop();
700 }
701 }
702 order
703}
704
705#[cfg(test)]
706#[allow(
707 clippy::expect_used,
708 reason = "tests assert on specific error variants; a wrong variant should fail loudly"
709)]
710mod tests {
711 use crate::function::{BlockData, Function, ValueData, ValueDef};
712 use crate::inst::Inst;
713 use crate::{BinOp, Block, Builder, Terminator, Type, UnOp, Value};
714
715 use super::ValidationError;
716 use alloc::string::ToString;
717 use alloc::vec;
718
719 #[test]
720 fn test_valid_straight_line_function_passes() {
721 let mut b = Builder::new("add", &[Type::Int, Type::Int], Type::Int);
722 let x = b.block_params(b.entry())[0];
723 let y = b.block_params(b.entry())[1];
724 let sum = b.bin(BinOp::Add, x, y);
725 b.ret(Some(sum));
726 assert_eq!(b.finish().validate(), Ok(()));
727 }
728
729 #[test]
730 fn test_valid_diamond_with_block_params_passes() {
731 let mut b = Builder::new("max", &[Type::Int, Type::Int], Type::Int);
732 let a = b.block_params(b.entry())[0];
733 let c = b.block_params(b.entry())[1];
734 let join = b.create_block(&[Type::Int]);
735 let then_blk = b.create_block(&[]);
736 let else_blk = b.create_block(&[]);
737 let cond = b.bin(BinOp::Lt, a, c);
738 b.branch(cond, then_blk, &[], else_blk, &[]);
739 b.switch_to(then_blk);
740 b.jump(join, &[c]);
741 b.switch_to(else_blk);
742 b.jump(join, &[a]);
743 b.switch_to(join);
744 let r = b.block_params(join)[0];
745 b.ret(Some(r));
746 assert_eq!(b.finish().validate(), Ok(()));
747 }
748
749 #[test]
750 fn test_valid_loop_passes() {
751 let mut b = Builder::new("loop", &[Type::Int], Type::Unit);
753 let start = b.block_params(b.entry())[0];
754 let header = b.create_block(&[Type::Int]);
755 let body = b.create_block(&[]);
756 let exit = b.create_block(&[]);
757 b.jump(header, &[start]);
758 b.switch_to(header);
759 let i = b.block_params(header)[0];
760 let zero = b.iconst(0);
761 let more = b.bin(BinOp::Gt, i, zero);
762 b.branch(more, body, &[], exit, &[]);
763 b.switch_to(body);
764 let one = b.iconst(1);
765 let next = b.bin(BinOp::Sub, i, one);
766 b.jump(header, &[next]);
767 b.switch_to(exit);
768 b.ret(None);
769 assert_eq!(b.finish().validate(), Ok(()));
770 }
771
772 #[test]
773 fn test_missing_terminator_is_rejected() {
774 let b = Builder::new("f", &[], Type::Unit);
775 let func = b.finish();
777 assert_eq!(
778 func.validate(),
779 Err(ValidationError::MissingTerminator {
780 block: func.entry()
781 })
782 );
783 }
784
785 #[test]
786 fn test_arg_count_mismatch_is_rejected() {
787 let mut b = Builder::new("f", &[], Type::Int);
788 let exit = b.create_block(&[Type::Int]);
789 let n = b.iconst(1);
790 b.jump(exit, &[]); b.switch_to(exit);
792 let r = b.block_params(exit)[0];
793 b.ret(Some(r));
794 let _ = n;
795 assert_eq!(
796 b.finish().validate(),
797 Err(ValidationError::ArgCountMismatch {
798 block: exit,
799 expected: 1,
800 found: 0,
801 })
802 );
803 }
804
805 #[test]
806 fn test_operand_type_mismatch_is_rejected() {
807 let mut b = Builder::new("f", &[Type::Int, Type::Bool], Type::Int);
808 let x = b.block_params(b.entry())[0];
809 let flag = b.block_params(b.entry())[1];
810 let bad = b.bin(BinOp::Add, x, flag); b.ret(Some(bad));
812 assert_eq!(
813 b.finish().validate(),
814 Err(ValidationError::TypeMismatch {
815 value: flag,
816 expected: Type::Int,
817 found: Type::Bool,
818 })
819 );
820 }
821
822 #[test]
823 fn test_non_numeric_arithmetic_is_rejected() {
824 let mut b = Builder::new("f", &[Type::Bool, Type::Bool], Type::Bool);
825 let p = b.block_params(b.entry())[0];
826 let q = b.block_params(b.entry())[1];
827 let bad = b.bin(BinOp::Mul, p, q); b.ret(Some(bad));
829 assert_eq!(
830 b.finish().validate(),
831 Err(ValidationError::NotNumeric {
832 value: p,
833 found: Type::Bool,
834 })
835 );
836 }
837
838 #[test]
839 fn test_non_bool_condition_is_rejected() {
840 let mut b = Builder::new("f", &[Type::Int], Type::Unit);
841 let x = b.block_params(b.entry())[0];
842 let yes = b.create_block(&[]);
843 let no = b.create_block(&[]);
844 b.branch(x, yes, &[], no, &[]); b.switch_to(yes);
846 b.ret(None);
847 b.switch_to(no);
848 b.ret(None);
849 assert_eq!(
850 b.finish().validate(),
851 Err(ValidationError::TypeMismatch {
852 value: x,
853 expected: Type::Bool,
854 found: Type::Int,
855 })
856 );
857 }
858
859 #[test]
860 fn test_return_type_mismatch_is_rejected() {
861 let mut b = Builder::new("f", &[Type::Bool], Type::Int);
862 let flag = b.block_params(b.entry())[0];
863 b.ret(Some(flag)); assert_eq!(
865 b.finish().validate(),
866 Err(ValidationError::TypeMismatch {
867 value: flag,
868 expected: Type::Int,
869 found: Type::Bool,
870 })
871 );
872 }
873
874 #[test]
875 fn test_return_without_value_in_non_unit_function_is_rejected() {
876 let mut b = Builder::new("f", &[], Type::Int);
877 b.ret(None);
878 assert_eq!(
879 b.finish().validate(),
880 Err(ValidationError::ReturnValueExpected {
881 expected: Type::Int
882 })
883 );
884 }
885
886 #[test]
887 fn test_branch_to_entry_is_rejected() {
888 let mut b = Builder::new("f", &[], Type::Unit);
889 let entry = b.entry();
890 b.jump(entry, &[]); assert_eq!(
892 b.finish().validate(),
893 Err(ValidationError::EntryBranchTarget { block: entry })
894 );
895 }
896
897 #[test]
898 fn test_use_before_def_across_blocks_is_rejected() {
899 let mut b = Builder::new("f", &[], Type::Int);
901 let entry = b.entry();
902 let other = b.create_block(&[]);
903 b.switch_to(other);
904 let v = b.iconst(7); b.ret(Some(v));
906 b.switch_to(entry);
907 b.ret(Some(v)); assert_eq!(
909 b.finish().validate(),
910 Err(ValidationError::UseBeforeDef {
911 value: v,
912 block: entry
913 })
914 );
915 }
916
917 #[test]
918 fn test_value_defined_in_sibling_branch_is_rejected() {
919 let mut b = Builder::new("f", &[Type::Bool], Type::Int);
922 let cond = b.block_params(b.entry())[0];
923 let then_blk = b.create_block(&[]);
924 let else_blk = b.create_block(&[]);
925 let join = b.create_block(&[Type::Int]);
926 b.branch(cond, then_blk, &[], else_blk, &[]);
927
928 b.switch_to(then_blk);
929 let secret = b.iconst(7); b.jump(join, &[secret]);
931
932 b.switch_to(else_blk);
933 b.jump(join, &[secret]); b.switch_to(join);
936 let r = b.block_params(join)[0];
937 b.ret(Some(r));
938
939 assert_eq!(
940 b.finish().validate(),
941 Err(ValidationError::UseBeforeDef {
942 value: secret,
943 block: else_blk,
944 })
945 );
946 }
947
948 #[test]
949 fn test_value_from_dominating_block_is_visible_deep_below() {
950 let mut b = Builder::new("f", &[Type::Int], Type::Int);
953 let base = b.iconst(100); let outer_then = b.create_block(&[]);
955 let outer_else = b.create_block(&[]);
956 let inner_then = b.create_block(&[]);
957 let inner_else = b.create_block(&[]);
958 let join = b.create_block(&[Type::Int]);
959
960 let p = b.block_params(b.entry())[0];
961 let zero = b.iconst(0);
962 let c0 = b.bin(BinOp::Gt, p, zero);
963 b.branch(c0, outer_then, &[], outer_else, &[]);
964
965 b.switch_to(outer_then);
966 let c1 = b.bin(BinOp::Lt, p, base); b.branch(c1, inner_then, &[], inner_else, &[]);
968
969 b.switch_to(inner_then);
970 b.jump(join, &[base]); b.switch_to(inner_else);
972 b.jump(join, &[base]);
973
974 b.switch_to(outer_else);
975 b.jump(join, &[base]);
976
977 b.switch_to(join);
978 let r = b.block_params(join)[0];
979 b.ret(Some(r));
980
981 assert_eq!(b.finish().validate(), Ok(()));
982 }
983
984 #[test]
985 fn test_out_of_range_value_is_rejected() {
986 let entry = Block::from_raw(0);
988 let blocks = vec![BlockData {
989 params: vec![],
990 insts: vec![],
991 term: Some(Terminator::Return(Some(Value::from_raw(5)))),
992 }];
993 let func = Function::from_parts(
994 "f".to_string(),
995 vec![],
996 Type::Int,
997 entry,
998 blocks,
999 vec![], );
1001 assert_eq!(
1002 func.validate(),
1003 Err(ValidationError::ValueOutOfRange {
1004 value: Value::from_raw(5)
1005 })
1006 );
1007 }
1008
1009 #[test]
1010 fn test_empty_function_is_rejected_without_panicking() {
1011 let func = Function::from_parts(
1014 "empty".to_string(),
1015 vec![],
1016 Type::Unit,
1017 Block::from_raw(0),
1018 vec![],
1019 vec![],
1020 );
1021 assert_eq!(
1022 func.validate(),
1023 Err(ValidationError::BlockOutOfRange {
1024 block: Block::from_raw(0)
1025 })
1026 );
1027 }
1028
1029 #[test]
1030 fn test_out_of_range_entry_is_rejected_without_panicking() {
1031 let blocks = vec![BlockData {
1033 params: vec![],
1034 insts: vec![],
1035 term: Some(Terminator::Return(None)),
1036 }];
1037 let func = Function::from_parts(
1038 "f".to_string(),
1039 vec![],
1040 Type::Unit,
1041 Block::from_raw(7), blocks,
1043 vec![],
1044 );
1045 assert_eq!(
1046 func.validate(),
1047 Err(ValidationError::BlockOutOfRange {
1048 block: Block::from_raw(7)
1049 })
1050 );
1051 }
1052
1053 #[test]
1054 fn test_out_of_range_block_is_rejected() {
1055 let entry = Block::from_raw(0);
1057 let blocks = vec![BlockData {
1058 params: vec![],
1059 insts: vec![],
1060 term: Some(Terminator::Jump(Block::from_raw(9), vec![])),
1061 }];
1062 let func = Function::from_parts("f".to_string(), vec![], Type::Unit, entry, blocks, vec![]);
1063 assert_eq!(
1064 func.validate(),
1065 Err(ValidationError::BlockOutOfRange {
1066 block: Block::from_raw(9)
1067 })
1068 );
1069 }
1070
1071 #[test]
1072 fn test_wrong_result_type_is_rejected() {
1073 let entry = Block::from_raw(0);
1077 let values = vec![ValueData {
1078 ty: Type::Bool, def: ValueDef::Inst(entry, Inst::Iconst(1)),
1080 }];
1081 let blocks = vec![BlockData {
1082 params: vec![],
1083 insts: vec![Value::from_raw(0)],
1084 term: Some(Terminator::Return(Some(Value::from_raw(0)))),
1085 }];
1086 let func = Function::from_parts("f".to_string(), vec![], Type::Bool, entry, blocks, values);
1087 assert_eq!(
1088 func.validate(),
1089 Err(ValidationError::TypeMismatch {
1090 value: Value::from_raw(0),
1091 expected: Type::Int,
1092 found: Type::Bool,
1093 })
1094 );
1095 }
1096
1097 #[test]
1098 fn test_value_listed_as_param_but_defined_as_inst_is_rejected() {
1099 let entry = Block::from_raw(0);
1102 let values = vec![ValueData {
1103 ty: Type::Int,
1104 def: ValueDef::Inst(entry, Inst::Iconst(0)), }];
1106 let blocks = vec![BlockData {
1107 params: vec![Value::from_raw(0)], insts: vec![],
1109 term: Some(Terminator::Return(None)),
1110 }];
1111 let func = Function::from_parts("f".to_string(), vec![], Type::Unit, entry, blocks, values);
1112 assert_eq!(
1113 func.validate(),
1114 Err(ValidationError::InconsistentDefinition {
1115 value: Value::from_raw(0)
1116 })
1117 );
1118 }
1119
1120 #[test]
1121 fn test_out_of_range_block_param_handle_is_rejected() {
1122 let entry = Block::from_raw(0);
1124 let blocks = vec![BlockData {
1125 params: vec![Value::from_raw(9)], insts: vec![],
1127 term: Some(Terminator::Return(None)),
1128 }];
1129 let func = Function::from_parts("f".to_string(), vec![], Type::Unit, entry, blocks, vec![]);
1130 assert_eq!(
1131 func.validate(),
1132 Err(ValidationError::ValueOutOfRange {
1133 value: Value::from_raw(9)
1134 })
1135 );
1136 }
1137
1138 #[test]
1139 fn test_not_operator_requires_bool() {
1140 let mut b = Builder::new("f", &[Type::Int], Type::Int);
1141 let x = b.block_params(b.entry())[0];
1142 let bad = b.un(UnOp::Not, x); b.ret(Some(bad));
1144 assert!(matches!(
1145 b.finish().validate(),
1146 Err(ValidationError::TypeMismatch { .. })
1147 ));
1148 }
1149
1150 #[test]
1151 fn test_error_display_is_human_readable() {
1152 let e = ValidationError::MissingTerminator {
1153 block: Block::from_raw(2),
1154 };
1155 assert_eq!(e.to_string(), "block b2 has no terminator");
1156 }
1157}