1use std::fmt;
43
44use rucc_base::Interner;
45use rucc_target::{Slot, TargetInfo};
46
47use crate::func::Func;
48use crate::inst::{
49 Abi, Block, CallInfo, Def, Inst, MetaNode, Param, PlaneNode, Signature, VaInfo, Value,
50};
51use crate::module::{Alias, AliasKind, DataLayout, Datum, Global, Module, SymbolRef};
52use crate::{Extra, MemOrder, Meta, Opcode, Type};
53
54#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct VerifyError {
57 pub at: String,
60 pub message: String,
62}
63
64impl fmt::Display for VerifyError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "{}: {}", self.at, self.message)
67 }
68}
69
70impl std::error::Error for VerifyError {}
71
72pub fn verify(module: &Module, names: &Interner) -> Result<(), Vec<VerifyError>> {
79 let mut verifier = Verifier::new(module, names);
80 verifier.module();
81 verifier.finish()
82}
83
84pub fn verify_func<'a>(
93 module: &'a Module,
94 func: &'a Func,
95 names: &'a Interner,
96) -> Result<(), Vec<VerifyError>> {
97 let mut verifier = Verifier::new(module, names);
98 verifier.func(func);
99 verifier.finish()
100}
101
102struct Verifier<'a> {
104 module: &'a Module,
105 names: &'a Interner,
106 errors: Vec<VerifyError>,
107 func: Option<&'a Func>,
110 block: Option<Block>,
111 inst: Option<Inst>,
112}
113
114impl<'a> Verifier<'a> {
115 fn new(module: &'a Module, names: &'a Interner) -> Self {
116 Verifier { module, names, errors: Vec::new(), func: None, block: None, inst: None }
117 }
118
119 fn finish(self) -> Result<(), Vec<VerifyError>> {
120 if self.errors.is_empty() { Ok(()) } else { Err(self.errors) }
121 }
122
123 fn module(&mut self) {
126 let implied = DataLayout::for_target(&TargetInfo::for_tuple(self.module.tuple));
127 if self.module.datalayout != implied {
128 self.at(
129 format!("@{}", self.names.resolve(self.module.name)),
130 format!(
131 "the datalayout is `{}` and {} implies `{implied}`",
132 self.module.datalayout, self.module.tuple
133 ),
134 );
135 }
136
137 for id in self.module.globals() {
138 self.global(&self.module[id]);
139 }
140 for id in self.module.aliases() {
141 self.alias(&self.module[id]);
142 }
143 for id in self.module.funcs() {
144 self.func(&self.module[id]);
145 }
146 self.metadata();
147 }
148
149 fn global(&mut self, global: &Global) {
150 let at = format!("@{}", self.names.resolve(global.name));
151 if !global.align.is_power_of_two() {
152 self.at(
153 at.clone(),
154 format!("an alignment is a power of two and this is {}", global.align),
155 );
156 }
157 let Some(init) = global.init else { return };
163 let mut size = 0;
164 for &datum in &self.module[init] {
165 size += datum.size(self.module);
166 if let Datum::Scalar { ty, .. } = datum {
172 if ty.bits() == 0 {
173 self.at(
174 at.clone(),
175 format!("a scalar in an image has a width and {ty} has none"),
176 );
177 }
178 }
179 if let Datum::Addr(reloc) = datum {
180 let bytes = self.module[reloc].size;
181 if !matches!(bytes, 1 | 2 | 4 | 8) {
182 self.at(
183 at.clone(),
184 format!(
185 "an address is written as 1, 2, 4 or 8 bytes and this one as {bytes}"
186 ),
187 );
188 }
189 }
190 }
191 if size != global.size {
192 self.at(at, format!("the image is {size} bytes and the global is {}", global.size));
193 }
194 }
195
196 fn alias(&mut self, alias: &Alias) {
197 let at = format!("@{}", self.names.resolve(alias.name));
198 if alias.name == alias.target {
199 self.at(at, "an alias to itself");
200 return;
201 }
202 let Some(found) = self.module.lookup(alias.target) else { return };
205 if alias.kind == AliasKind::IFunc && !matches!(found, SymbolRef::Func(_)) {
206 self.at(at, "an ifunc resolves through a function and this target is not one");
207 }
208 }
209
210 fn metadata(&mut self) {
211 for node in self.module.metadata() {
212 let at = format!("!{}", node.raw());
213 if let Some(named) = self.module[node].points_at() {
214 if named.raw() >= node.raw() {
215 self.at(
220 at.clone(),
221 format!(
222 "a metadata node names one that comes before it and this one is !{}",
223 named.raw()
224 ),
225 );
226 continue;
227 }
228 if self.module[node].plane().is_some() && self.module[named].plane().is_some() {
229 self.at(
232 at.clone(),
233 format!("a plane entry names a type and !{} is a plane entry", named.raw()),
234 );
235 }
236 }
237 if let MetaNode::Plane(PlaneNode::PointerSlot(k)) = self.module[node] {
238 let bytes = self.module.datalayout.pointer_bits / 8;
241 if u32::from(k) >= bytes {
242 self.at(
243 at,
244 format!("a pointer on this target is {bytes} bytes and this is byte {k}"),
245 );
246 }
247 }
248 }
249 }
250
251 fn func(&mut self, func: &'a Func) {
254 self.func = Some(func);
255 self.block = None;
256 self.inst = None;
257
258 if let Some((one, other)) = func.attrs.conflict() {
259 self.error(format!("`{one}` and `{other}` cannot both be true of a function"));
260 }
261 if func.is_declaration() {
262 self.func = None;
263 return;
264 }
265 if !self.bounds(func) {
268 self.func = None;
269 return;
270 }
271
272 for signature in func.signatures() {
273 self.signature(signature);
274 }
275
276 let entry = func.entry().expect("a function with blocks has a first one");
277 let params: Vec<Type> = func[entry].params.iter().map(|&value| func[value].ty).collect();
278 let want: Vec<Type> = func.signature().param_types().collect();
279 if params != want {
280 self.error(format!(
281 "the entry block takes {} and the signature says {}",
282 types(¶ms),
283 types(&want)
284 ));
285 }
286
287 let doms = Doms::new(func);
288 let layout = Layout::new(func);
289 for block in func.blocks() {
290 self.block = Some(block);
291 self.block(func, block, &doms, &layout);
292 }
293 self.block = None;
294 self.inst = None;
295 self.mem_chain(func);
296 self.capabilities(func);
297 self.facts(func, &doms, &layout);
298 self.func = None;
299 }
300
301 fn signature(&mut self, signature: &Signature) {
308 for (index, param) in signature.params.iter().enumerate() {
309 let at = format!("parameter {}", index + 1);
310 self.abi(&at, param);
311 match param.abi {
312 Abi::Sret { .. } if index > 0 => {
313 self.error("sret is the first parameter and this one is not");
317 }
318 Abi::Sret { .. } if !signature.returns.is_empty() => {
319 self.error("a signature returning through sret returns nothing else");
320 }
321 _ => {}
322 }
323 }
324 for (index, param) in signature.returns.iter().enumerate() {
325 let at = format!("result {}", index + 1);
326 self.abi(&at, param);
327 if param.abi.indirect() {
328 self.error(format!("{at} travels indirectly and a result cannot"));
331 }
332 }
333 }
334
335 fn varargs(
343 &mut self,
344 func: &'a Func,
345 info: &CallInfo,
346 passed: usize,
347 arg: impl Fn(usize) -> Type,
348 ) {
349 let varargs = &func[info.varargs];
350 if varargs.is_empty() {
351 return;
352 }
353 let named = func[info.signature].params.len();
354 if !func[info.signature].variadic {
355 self.error("this call is not variadic and says how a variadic argument travels");
356 return;
357 }
358 if varargs.len() != passed.saturating_sub(named) {
359 self.error(format!(
360 "the call passes {} arguments the signature does not name and says how {} travel",
361 passed.saturating_sub(named),
362 varargs.len()
363 ));
364 return;
365 }
366 for (index, &abi) in varargs.iter().enumerate() {
367 let at = format!("argument {}", named + index + 1);
368 if matches!(abi, Abi::Sret { .. }) {
369 self.error(format!("{at} is an sret and only a parameter can be one"));
370 continue;
371 }
372 self.abi(&at, &Param { ty: arg(index + named), abi });
373 }
374 }
375
376 fn abi(&mut self, at: &str, param: &Param) {
378 if param.ty.is_mem() {
379 self.error(format!("{at} is memory, and no function takes or returns memory"));
383 }
384 if param.ty.is_cap() {
385 self.error(format!("{at} is a capability and a capability does not cross a call"));
390 }
391 match param.abi {
392 Abi::Plain => {}
393 Abi::Sext | Abi::Zext => {
394 if !param.ty.is_int() || param.ty.is_vector() {
395 self.error(format!("{at} is extended and {} is not an integer", param.ty));
396 }
397 }
398 Abi::ByVal { size, align } | Abi::Sret { size, align } => {
399 if !param.ty.is_ptr() {
400 self.error(format!(
401 "{at} travels indirectly and {} is not a pointer",
402 param.ty
403 ));
404 }
405 if !align.is_power_of_two() {
406 self.error(format!("an alignment is a power of two and this is {align}"));
407 }
408 if size == 0 {
409 self.error(format!("{at} travels indirectly and has no size"));
410 }
411 }
412 }
413 }
414
415 fn bounds(&mut self, func: &'a Func) -> bool {
421 let counts = func.counts();
422 let before = self.errors.len();
423 for block in func.blocks() {
424 self.block = Some(block);
425 for &value in &func[block].params {
426 if value.index() >= counts.values {
427 self.error(format!(
428 "parameter %{} is not a value of this function",
429 value.raw()
430 ));
431 }
432 }
433 for inst in func.insts(block) {
434 self.inst = Some(inst);
435 for &value in &func[func[inst].args] {
436 if value.index() >= counts.values {
437 self.error(format!("%{} is not a value of this function", value.raw()));
438 }
439 }
440 for value in func[inst].results() {
441 if value.index() >= counts.values {
442 self.error(format!("%{} is not a value of this function", value.raw()));
443 }
444 }
445 for call in func.successors(inst) {
446 if call.block.index() >= counts.blocks {
447 self.error(format!(
448 "block{} is not a block of this function",
449 call.block.raw()
450 ));
451 }
452 for &value in &func[call.args] {
453 if value.index() >= counts.values {
454 self.error(format!("%{} is not a value of this function", value.raw()));
455 }
456 }
457 }
458 }
459 self.inst = None;
460 }
461 self.block = None;
462 self.errors.len() == before
463 }
464
465 fn block(&mut self, func: &'a Func, block: Block, doms: &Doms, layout: &Layout) {
466 if !doms.reaches(block) {
467 self.error("this block is not reachable and has not been deleted");
468 }
469 if block == func.entry().expect("checked in func") {
470 for other in func.blocks() {
471 let last = func[other].last;
472 if last.is_some_and(|inst| func.successors(inst).any(|call| call.block == block)) {
473 self.error("the entry block is branched to, and it takes the arguments");
474 }
475 }
476 }
477
478 let mut seen_terminator = false;
479 for inst in func.insts(block) {
480 self.inst = Some(inst);
481 if seen_terminator {
482 self.error("this comes after the block's terminator");
483 }
484 seen_terminator |= func.is_terminator(inst);
485 self.inst(func, inst, doms, layout);
486 }
487 self.inst = None;
488 if !seen_terminator {
489 self.error("this block does not end in a terminator");
490 }
491 }
492
493 fn inst(&mut self, func: &'a Func, inst: Inst, doms: &Doms, layout: &Layout) {
494 let data = &func[inst];
495 let opcode = data.opcode;
496
497 let stray = data.flags.without(crate::Flags::legal_on(opcode));
498 if !stray.is_empty() {
499 let names: Vec<&str> = stray.iter().map(|(_, name)| name).collect();
500 self.error(format!("{} does not read `{}`", opcode.name(), names.join("`, `")));
501 }
502 if data.extra.kind() != opcode.extra_kind() {
503 self.error(format!(
506 "{} carries {} and this one carries {}",
507 opcode.name(),
508 opcode.extra_kind().name(),
509 data.extra.kind().name()
510 ));
511 }
512 if let Some(want) = opcode.results() {
513 let want = want + u8::from(extra_mem(func, inst));
517 if want != data.results {
518 self.error(format!(
519 "{} produces {want} values and this one produces {}",
520 opcode.name(),
521 data.results
522 ));
523 }
524 }
525
526 if let Extra::Node(at) = data.extra {
527 if at.index() >= self.module.counts().metadata {
528 self.error(format!("!{} is not a metadata node of this module", at.raw()));
529 } else {
530 self.names_the_right_node(opcode, at);
531 }
532 }
533
534 self.uses(func, inst, doms, layout);
535 self.branches(func, inst);
536 self.memory(func, inst);
537 self.mem_ssa(func, inst);
538 self.shape(func, inst);
539 }
540
541 fn uses(&mut self, func: &'a Func, inst: Inst, doms: &Doms, layout: &Layout) {
543 let block = layout.block_of(inst).expect("walking the blocks");
544 let check = |verifier: &mut Self, value: Value| match func[value].def {
545 Def::Param { block: def, .. } => {
546 if !doms.dominates(def, block) {
547 verifier.error(format!(
548 "%{} arrives at block{} and does not reach here",
549 value.raw(),
550 def.raw()
551 ));
552 }
553 }
554 Def::Result { inst: def, .. } => {
555 let Some(def_block) = layout.block_of(def) else {
556 verifier.error(format!(
557 "%{} is produced by an instruction that is not in the function",
558 value.raw()
559 ));
560 return;
561 };
562 let reaches = if def_block == block {
563 layout.position(def) < layout.position(inst)
564 } else {
565 doms.dominates(def_block, block)
566 };
567 if !reaches {
568 verifier.error(format!(
569 "%{} is produced in block{} and does not reach here",
570 value.raw(),
571 def_block.raw()
572 ));
573 }
574 }
575 };
576 for &value in &func[func[inst].args] {
577 check(self, value);
578 }
579 for call in func.successors(inst) {
580 for &value in &func[call.args] {
581 check(self, value);
582 }
583 }
584 }
585
586 fn branches(&mut self, func: &'a Func, inst: Inst) {
588 if func[inst].opcode == Opcode::BlockAddr {
589 return;
593 }
594 for call in func.successors(inst) {
595 let params = &func[call.block].params;
596 let args = &func[call.args];
597 if params.len() != args.len() {
598 self.error(format!(
599 "block{} takes {} arguments and this branch passes {}",
600 call.block.raw(),
601 params.len(),
602 args.len()
603 ));
604 continue;
605 }
606 for (index, (¶m, &arg)) in params.iter().zip(args).enumerate() {
607 let (want, got) = (func[param].ty, func[arg].ty);
608 if want != got {
609 self.error(format!(
610 "argument {} to block{} is {want} and this one is {got}",
611 index + 1,
612 call.block.raw()
613 ));
614 }
615 }
616 }
617 if let Extra::Switch(info) = func[inst].extra {
618 let switch = &func[info];
619 let targets = &func[switch.targets];
620 let cases = &func[switch.cases];
621 if targets.len() != cases.len() + 1 {
622 self.error(format!(
623 "a switch has one target per case and a default, and this one has {} targets for {} cases",
624 targets.len(),
625 cases.len()
626 ));
627 }
628 for (index, case) in cases.iter().enumerate() {
629 if cases[..index].contains(case) {
630 self.error("two cases of this switch have the same value");
631 }
632 }
633 }
634 }
635
636 fn names_the_right_node(&mut self, opcode: Opcode, at: Meta) {
643 let wants_plane = matches!(opcode, Opcode::CheckType | Opcode::MetaType);
644 let is_plane = self.module[at].plane().is_some();
645 if wants_plane != is_plane {
646 let (asked, got) = if wants_plane {
647 ("a plane entry", "an aliasing node")
648 } else {
649 ("an aliasing node", "a plane entry")
650 };
651 self.error(format!("{} names {asked} and !{} is {got}", opcode.name(), at.raw()));
652 }
653 }
654
655 fn memory(&mut self, func: &'a Func, inst: Inst) {
657 let opcode = func[inst].opcode;
658 let info = match func[inst].extra {
659 Extra::Mem(at) => func[at],
660 Extra::Rmw(_, at) => func[at],
661 Extra::VaObject(at) => {
662 let object = func[at];
663 self.slots(func, object);
664 func[object.mem]
665 }
666 Extra::Order(order) => {
667 if !order.is_valid_for_rmw() {
668 self.error("a fence is not a fence unless it orders something");
669 }
670 return;
671 }
672 _ => return,
673 };
674 if !info.align.is_power_of_two() {
675 self.error(format!("an alignment is a power of two and this is {}", info.align));
676 }
677 if let Some(tbaa) = info.tbaa {
678 if tbaa.index() >= self.module.counts().metadata {
679 self.error(format!("!{} is not a metadata node of this module", tbaa.raw()));
680 } else {
681 self.names_the_right_node(opcode, tbaa);
682 }
683 }
684 if info.owns != 0 && !matches!(opcode, Opcode::Store | Opcode::AtomicStore) {
685 self.error(format!(
688 "a {} owns no padding, since only a store records any",
689 opcode.name()
690 ));
691 }
692 if info.restrict.clique == 0 && info.restrict.base != 0 {
693 self.error(format!(
696 "restrict base {} is in no clique, and a base means nothing without one",
697 info.restrict.base
698 ));
699 }
700 let ok = match opcode {
701 Opcode::AtomicLoad => info.order.is_valid_for_load(),
702 Opcode::AtomicStore => info.order.is_valid_for_store(),
703 Opcode::AtomicRmw | Opcode::Cmpxchg => info.order.is_valid_for_rmw(),
704 _ => info.order == MemOrder::NotAtomic,
707 };
708 if !ok {
709 self.error(format!("{} cannot be asked for {}", opcode.name(), info.order));
710 }
711 if matches!(opcode, Opcode::Memcpy | Opcode::Memmove | Opcode::Memset) && info.size == 0 {
712 self.error(format!("{} moves no bytes", opcode.name()));
713 }
714 }
715
716 fn mem_ssa(&mut self, func: &'a Func, inst: Inst) {
723 let opcode = func[inst].opcode;
724 let takes = func.mem_in(inst).is_some();
725 let gives = func.mem_out(inst).is_some();
726
727 if takes && !opcode.touches_memory() {
728 self.error(format!(
731 "{} does not touch memory and is on the memory chain",
732 opcode.name()
733 ));
734 }
735 if gives && !opcode.writes_memory() && opcode != Opcode::MemEntry {
736 self.error(format!(
737 "{} does not write memory and produces a new version of it",
738 opcode.name()
739 ));
740 }
741 if opcode.writes_memory() && takes != gives {
742 self.error(format!(
745 "{} takes {} and produces {}, and a write does both or neither",
746 opcode.name(),
747 if takes { "memory" } else { "no memory" },
748 if gives { "a new version" } else { "none" }
749 ));
750 }
751
752 let args = &func[func[inst].args];
756 let ordinary = args.len() - usize::from(takes);
757 for (index, &arg) in args[..ordinary].iter().enumerate() {
758 if func[arg].ty.is_mem() {
759 self.error(format!(
760 "%{} is memory and operand {} of {} is not the memory operand",
761 arg.raw(),
762 index + 1,
763 opcode.name()
764 ));
765 }
766 }
767 }
768
769 fn mem_chain(&mut self, func: &'a Func) {
778 let mut entries = Vec::new();
779 let mut on = Vec::new();
780 let mut off = Vec::new();
781 for block in func.blocks() {
782 for inst in func.insts(block) {
783 if func[inst].opcode == Opcode::MemEntry {
784 entries.push((block, inst));
785 } else if func[inst].opcode.touches_memory() {
786 if func.carries_mem(inst) { &mut on } else { &mut off }.push((block, inst));
787 }
788 }
789 }
790
791 if let Some(&(block, inst)) = entries.get(1) {
792 self.block = Some(block);
793 self.inst = Some(inst);
794 self.error("a function starts with one version of memory and this is a second one");
795 }
796 if on.is_empty() {
797 if let Some(&(block, inst)) = entries.first() {
798 self.block = Some(block);
799 self.inst = Some(inst);
800 self.error("nothing in this function is on the memory chain, and this starts one");
801 }
802 } else {
803 if entries.is_empty() {
804 let (block, inst) = on[0];
805 self.block = Some(block);
806 self.inst = Some(inst);
807 self.error("this is on the memory chain and the function has no mem_entry");
808 }
809 if let Some(&(block, inst)) = off.first() {
810 self.block = Some(block);
811 self.inst = Some(inst);
812 self.error(format!(
813 "{} touches memory and is not on the chain, and the rest of this function is",
814 func[inst].opcode.name()
815 ));
816 }
817 }
818 self.block = None;
819 self.inst = None;
820 }
821
822 fn capabilities(&mut self, func: &'a Func) {
835 for block in func.blocks() {
836 self.block = Some(block);
837 for inst in func.insts(block) {
838 self.inst = Some(inst);
839 if func[inst].opcode.makes_capability() {
840 continue;
841 }
842 for value in func[inst].results() {
843 if func[value].ty.is_cap() {
844 self.error(format!(
845 "{} produces a capability and only the cap instructions do that",
846 func[inst].opcode.name()
847 ));
848 }
849 }
850 }
851 self.inst = None;
852 }
853 self.block = None;
854 }
855
856 fn facts(&mut self, func: &'a Func, doms: &Doms, layout: &Layout) {
868 for (value, facts) in func.known() {
869 let ty = func[value].ty;
870 if !ty.is_ptr() {
871 self.error(format!("%{} is said to be a pointer and it is {ty}", value.raw()));
872 }
873 match facts.align {
874 Some(align) if !align.is_power_of_two() => self.error(format!(
875 "%{} is said to be aligned to {align} and an alignment is a power of two",
876 value.raw()
877 )),
878 _ => {}
879 }
880 let Some(bounds) = facts.bounds else { continue };
881 let lo = func[bounds.lo].ty;
882 if !lo.is_ptr() {
883 self.error(format!(
884 "the range %{} is in starts at a pointer and %{} is {lo}",
885 value.raw(),
886 bounds.lo.raw()
887 ));
888 }
889 let ext = func[bounds.ext].ty;
890 if !ext.lane().is_int() {
891 self.error(format!(
892 "the range %{} is in has an integer extent and %{} is {ext}",
893 value.raw(),
894 bounds.ext.raw()
895 ));
896 }
897 for named in [bounds.lo, bounds.ext] {
898 if !self.reaches(func, doms, layout, named, value) {
899 self.error(format!(
900 "%{} names %{} and does not reach it",
901 value.raw(),
902 named.raw()
903 ));
904 }
905 }
906 }
907 }
908
909 fn reaches(
914 &self,
915 func: &'a Func,
916 doms: &Doms,
917 layout: &Layout,
918 value: Value,
919 at: Value,
920 ) -> bool {
921 let point = |of: Value| match func[of].def {
922 Def::Param { block, .. } => Some((block, None)),
923 Def::Result { inst, .. } => layout.block_of(inst).map(|block| (block, Some(inst))),
924 };
925 let (Some((from, def)), Some((to, use_at))) = (point(value), point(at)) else {
926 return true;
929 };
930 if from != to {
931 return doms.dominates(from, to);
932 }
933 match (def, use_at) {
934 (None, _) => true,
935 (Some(_), None) => false,
936 (Some(def), Some(use_at)) => layout.position(def) < layout.position(use_at),
937 }
938 }
939
940 fn slots(&mut self, func: &'a Func, object: VaInfo) {
946 let size = func[object.mem].size;
947 for &slot in &func[object.slots] {
948 let width = match slot {
949 Slot::Integer { size, .. } => u64::from(size),
950 Slot::Float { format, .. } => u64::from(format.width()).div_ceil(8),
951 };
952 if slot.offset() + width > size {
953 self.error(format!(
954 "a slot holds bytes {} to {} of an object of {size} bytes",
955 slot.offset(),
956 slot.offset() + width
957 ));
958 }
959 }
960 }
961
962 #[expect(clippy::too_many_lines, reason = "one arm per group of opcodes, and they differ")]
968 fn shape(&mut self, func: &'a Func, inst: Inst) {
969 let data = &func[inst];
970 let opcode = data.opcode;
971 let all = &func[data.args];
975 let args = &all[..all.len() - usize::from(func.mem_in(inst).is_some())];
976 let arity = args.len();
977 let arg = |n: usize| func[args[n]].ty;
978 let results = usize::from(data.results) - usize::from(extra_mem(func, inst));
979 let res = |n: usize| func[data.results().nth(n).expect("within the count")].ty;
980
981 match opcode {
982 Opcode::IConst => {
984 if self.takes(opcode, arity, 0) && results == 1 && !res(0).lane().is_int() {
985 self.error(format!(
986 "iconst produces an integer and this one produces {}",
987 res(0)
988 ));
989 }
990 }
991 Opcode::FConst => {
992 if self.takes(opcode, arity, 0) && results == 1 && !res(0).lane().is_float() {
993 self.error(format!(
994 "fconst produces a floating point value and this one produces {}",
995 res(0)
996 ));
997 }
998 }
999 Opcode::Splat => {
1000 if self.takes(opcode, arity, 0) && results == 1 && !res(0).is_vector() {
1001 self.error(format!("splat produces a vector and this one produces {}", res(0)));
1002 }
1003 }
1004 Opcode::GlobalAddr
1005 | Opcode::StackSave
1006 | Opcode::FrameAddress
1007 | Opcode::ReturnAddress => {
1008 if results == 1 && !res(0).is_ptr() {
1009 self.error(format!(
1010 "{} produces a pointer and this one produces {}",
1011 opcode.name(),
1012 res(0)
1013 ));
1014 }
1015 }
1016
1017 Opcode::Add
1019 | Opcode::Sub
1020 | Opcode::Mul
1021 | Opcode::SDiv
1022 | Opcode::UDiv
1023 | Opcode::SRem
1024 | Opcode::URem
1025 | Opcode::And
1026 | Opcode::Or
1027 | Opcode::Xor
1028 | Opcode::Shl
1029 | Opcode::LShr
1030 | Opcode::AShr => {
1031 if self.takes(opcode, arity, 2) {
1032 self.integer(opcode, arg(0), 0);
1033 self.agree(opcode, arg(0), arg(1));
1034 if results == 1 {
1035 self.produces(opcode, res(0), arg(0));
1036 }
1037 }
1038 }
1039
1040 Opcode::FAdd | Opcode::FSub | Opcode::FMul | Opcode::FDiv | Opcode::FRem => {
1042 if self.takes(opcode, arity, 2) {
1043 self.floating(opcode, arg(0), 0);
1044 self.agree(opcode, arg(0), arg(1));
1045 if results == 1 {
1046 self.produces(opcode, res(0), arg(0));
1047 }
1048 }
1049 }
1050 Opcode::FNeg => {
1051 if self.takes(opcode, arity, 1) {
1052 self.floating(opcode, arg(0), 0);
1053 if results == 1 {
1054 self.produces(opcode, res(0), arg(0));
1055 }
1056 }
1057 }
1058 Opcode::Fma => {
1059 if self.takes(opcode, arity, 3) {
1060 self.floating(opcode, arg(0), 0);
1061 self.agree(opcode, arg(0), arg(1));
1062 self.agree(opcode, arg(0), arg(2));
1063 if results == 1 {
1064 self.produces(opcode, res(0), arg(0));
1065 }
1066 }
1067 }
1068
1069 Opcode::Select => {
1074 if self.takes(opcode, arity, 3) {
1075 if arg(0).lane() != Type::I1 {
1076 self.error(format!(
1077 "operand 1 of select is the bit that chooses and is i1 or a vector of \
1078 i1, and this one is {}",
1079 arg(0)
1080 ));
1081 } else if arg(0).lanes() != 1 && arg(0).lanes() != arg(1).lanes() {
1082 self.error(format!(
1083 "operand 1 of select chooses one lane at a time, so it has {} lanes or \
1084 one, and this one has {}",
1085 arg(1).lanes(),
1086 arg(0).lanes()
1087 ));
1088 }
1089 self.agree(opcode, arg(1), arg(2));
1090 if results == 1 {
1091 self.produces(opcode, res(0), arg(1));
1092 }
1093 }
1094 }
1095
1096 Opcode::ICmp | Opcode::FCmp => {
1098 if self.takes(opcode, arity, 2) {
1099 if opcode == Opcode::FCmp {
1100 self.floating(opcode, arg(0), 0);
1101 } else if !arg(0).lane().is_int() && !arg(0).is_ptr() {
1102 self.error(format!(
1103 "operand 1 of icmp is an integer or a pointer and this one is {}",
1104 arg(0)
1105 ));
1106 }
1107 self.agree(opcode, arg(0), arg(1));
1108 if results == 1 {
1109 self.produces(opcode, res(0), arg(0).with_lane(Type::I1));
1110 }
1111 }
1112 }
1113
1114 Opcode::Trunc | Opcode::SExt | Opcode::ZExt => {
1116 if self.takes(opcode, arity, 1) && results == 1 {
1117 self.integer(opcode, arg(0), 0);
1118 self.lanes(opcode, res(0), arg(0));
1119 self.widens(opcode, res(0), arg(0), opcode != Opcode::Trunc);
1120 }
1121 }
1122 Opcode::FPTrunc | Opcode::FPExt => {
1123 if self.takes(opcode, arity, 1) && results == 1 {
1124 self.floating(opcode, arg(0), 0);
1125 self.lanes(opcode, res(0), arg(0));
1126 self.widens(opcode, res(0), arg(0), opcode == Opcode::FPExt);
1127 }
1128 }
1129 Opcode::FPToSI | Opcode::FPToUI => {
1130 if self.takes(opcode, arity, 1) && results == 1 {
1131 self.floating(opcode, arg(0), 0);
1132 self.lanes(opcode, res(0), arg(0));
1133 if !res(0).lane().is_int() {
1134 self.error(format!(
1135 "{} produces an integer and this one produces {}",
1136 opcode.name(),
1137 res(0)
1138 ));
1139 }
1140 }
1141 }
1142 Opcode::SIToFP | Opcode::UIToFP => {
1143 if self.takes(opcode, arity, 1) && results == 1 {
1144 self.integer(opcode, arg(0), 0);
1145 self.lanes(opcode, res(0), arg(0));
1146 if !res(0).lane().is_float() {
1147 self.error(format!(
1148 "{} produces a floating point value and this one produces {}",
1149 opcode.name(),
1150 res(0)
1151 ));
1152 }
1153 }
1154 }
1155 Opcode::PtrToInt => {
1156 if self.takes(opcode, arity, 1) && results == 1 {
1157 self.pointer(opcode, arg(0), 0);
1158 self.integer(opcode, res(0), 0);
1159 }
1160 }
1161 Opcode::IntToPtr => {
1162 if self.takes(opcode, arity, 1) && results == 1 {
1163 self.integer(opcode, arg(0), 0);
1164 if !res(0).is_ptr() {
1165 self.error(format!(
1166 "inttoptr produces a pointer and this one produces {}",
1167 res(0)
1168 ));
1169 }
1170 }
1171 }
1172 Opcode::Bitcast => {
1173 if self.takes(opcode, arity, 1) && results == 1 {
1174 let (from, to) = (arg(0), res(0));
1175 if from.is_ptr() != to.is_ptr() {
1176 self.error(
1179 "a bitcast between a pointer and a number is ptrtoint or inttoptr",
1180 );
1181 } else if width(from) != width(to) {
1182 self.error(format!("a bitcast keeps the width and {from} and {to} differ"));
1183 }
1184 }
1185 }
1186
1187 Opcode::MemEntry => {
1192 if self.takes(opcode, arity, 0) && results == 1 && !res(0).is_mem() {
1193 self.error(format!(
1194 "mem_entry produces memory and this one produces {}",
1195 res(0)
1196 ));
1197 }
1198 if func.block_of(inst) != func.entry() {
1199 self.error("mem_entry belongs in the entry block");
1200 }
1201 }
1202 Opcode::Alloca => {
1203 if arity > 1 {
1204 self.takes(opcode, arity, 1);
1205 } else if arity == 1 {
1206 self.integer(opcode, arg(0), 0);
1207 } else if func.block_of(inst) != func.entry() {
1208 self.error("an alloca of a fixed size belongs in the entry block");
1211 }
1212 if results == 1 && !res(0).is_ptr() {
1213 self.error(format!(
1214 "alloca produces a pointer and this one produces {}",
1215 res(0)
1216 ));
1217 }
1218 }
1219 Opcode::Load | Opcode::AtomicLoad => {
1220 if self.takes(opcode, arity, 1) {
1221 self.pointer(opcode, arg(0), 0);
1222 }
1223 if results == 1 && res(0).is_void() {
1224 self.error(format!("{} reads a value and void is not one", opcode.name()));
1225 }
1226 }
1227 Opcode::Store | Opcode::AtomicStore => {
1228 if self.takes(opcode, arity, 2) {
1229 if arg(0).is_void() {
1230 self.error(format!("{} writes a value and void is not one", opcode.name()));
1231 }
1232 self.pointer(opcode, arg(1), 1);
1233 }
1234 }
1235 Opcode::PtrAdd => {
1236 if self.takes(opcode, arity, 2) {
1237 self.pointer(opcode, arg(0), 0);
1238 self.integer(opcode, arg(1), 1);
1239 if results == 1 && !res(0).is_ptr() {
1240 self.error(format!(
1241 "ptr_add produces a pointer and this one produces {}",
1242 res(0)
1243 ));
1244 }
1245 }
1246 }
1247 Opcode::Memcpy | Opcode::Memmove => {
1248 if self.takes(opcode, arity, 2) {
1249 self.pointer(opcode, arg(0), 0);
1250 self.pointer(opcode, arg(1), 1);
1251 }
1252 }
1253 Opcode::Memset => {
1254 if self.takes(opcode, arity, 2) {
1255 self.pointer(opcode, arg(0), 0);
1256 self.integer(opcode, arg(1), 1);
1257 }
1258 }
1259 Opcode::AtomicRmw => {
1260 if self.takes(opcode, arity, 2) {
1261 self.pointer(opcode, arg(0), 0);
1262 self.integer(opcode, arg(1), 1);
1263 if results == 1 {
1264 self.produces(opcode, res(0), arg(1));
1265 }
1266 }
1267 }
1268 Opcode::Cmpxchg => {
1269 if self.takes(opcode, arity, 3) {
1270 self.pointer(opcode, arg(0), 0);
1271 self.agree(opcode, arg(1), arg(2));
1272 if results == 2 {
1273 self.produces(opcode, res(0), arg(1));
1274 self.produces(opcode, res(1), arg(1).with_lane(Type::I1));
1275 }
1276 }
1277 }
1278 Opcode::Fence | Opcode::Unreachable | Opcode::UnreachableHint => {
1279 self.takes(opcode, arity, 0);
1280 }
1281 Opcode::Prefetch | Opcode::StackRestore | Opcode::VaStart | Opcode::VaEnd => {
1282 if self.takes(opcode, arity, 1) {
1283 self.pointer(opcode, arg(0), 0);
1284 }
1285 }
1286 Opcode::VaCopy => {
1287 if self.takes(opcode, arity, 2) {
1288 self.pointer(opcode, arg(0), 0);
1289 self.pointer(opcode, arg(1), 1);
1290 }
1291 }
1292 Opcode::VaArg => {
1293 if self.takes(opcode, arity, 1) {
1294 self.pointer(opcode, arg(0), 0);
1295 }
1296 if results == 1 && res(0).is_void() {
1297 self.error("va_arg reads a value and void is not one");
1298 }
1299 }
1300 Opcode::VaObject => {
1301 if self.takes(opcode, arity, 1) {
1302 self.pointer(opcode, arg(0), 0);
1303 }
1304 if results == 1 && res(0) != Type::PTR {
1305 self.error(format!(
1306 "va_object answers where the object is and {} is not an address",
1307 res(0)
1308 ));
1309 }
1310 }
1311
1312 Opcode::Jump => {
1314 self.takes(opcode, arity, 0);
1315 self.targets(func, inst, 1);
1316 }
1317 Opcode::BrIf => {
1318 if self.takes(opcode, arity, 1) && arg(0) != Type::I1 {
1319 self.error(format!("br_if branches on an i1 and this one on {}", arg(0)));
1320 }
1321 self.targets(func, inst, 2);
1322 }
1323 Opcode::Switch => {
1324 if self.takes(opcode, arity, 1) {
1325 self.integer(opcode, arg(0), 0);
1326 }
1327 }
1328 Opcode::BlockAddr => {
1329 self.takes(opcode, arity, 0);
1330 self.targets(func, inst, 1);
1331 if results == 1 && !res(0).is_ptr() {
1332 self.error(format!(
1333 "block_addr produces a pointer and this one produces {}",
1334 res(0)
1335 ));
1336 }
1337 if func.successors(inst).any(|call| !func[call.args].is_empty()) {
1338 self.error("block_addr names a block and passes it arguments");
1341 }
1342 }
1343 Opcode::IndirectBr => {
1344 if self.takes(opcode, arity, 1) {
1345 self.pointer(opcode, arg(0), 0);
1346 }
1347 }
1350 Opcode::Return => {
1351 let want = &func.signature().returns;
1352 if arity != want.len() {
1353 self.error(format!(
1354 "the signature returns {} and this returns {arity}",
1355 want.len()
1356 ));
1357 } else {
1358 for (index, ty) in want.iter().map(|param| param.ty).enumerate() {
1359 if arg(index) != ty {
1360 self.error(format!(
1361 "result {} of the signature is {ty} and this returns {}",
1362 index + 1,
1363 arg(index)
1364 ));
1365 }
1366 }
1367 }
1368 }
1369
1370 Opcode::Call | Opcode::CallIndirect | Opcode::TailCall => {
1372 let Extra::Call(at) = data.extra else { return };
1373 let info = func[at];
1374 let signature = &func[info.signature];
1375 let indirect = usize::from(opcode == Opcode::CallIndirect);
1376 if indirect == 1 {
1377 if arity == 0 {
1378 self.error("call_indirect calls through a pointer and has no operands");
1379 return;
1380 }
1381 self.pointer(opcode, arg(0), 0);
1382 }
1383 let passed = arity - indirect;
1384 let enough = if signature.variadic {
1385 passed >= signature.params.len()
1386 } else {
1387 passed == signature.params.len()
1388 };
1389 if enough {
1390 for (index, ty) in signature.param_types().enumerate() {
1391 if arg(index + indirect) != ty {
1392 self.error(format!(
1393 "parameter {} of the signature is {ty} and this argument is {}",
1394 index + 1,
1395 arg(index + indirect)
1396 ));
1397 }
1398 }
1399 } else {
1400 self.error(format!(
1401 "the signature takes {}{} and this call passes {passed}",
1402 signature.params.len(),
1403 if signature.variadic { " or more" } else { "" }
1404 ));
1405 }
1406 self.varargs(func, &info, passed, |n| arg(n + indirect));
1407 if results != signature.returns.len() {
1408 self.error(format!(
1409 "the signature returns {} and this call produces {results}",
1410 signature.returns.len()
1411 ));
1412 } else {
1413 for (index, ty) in signature.return_types().enumerate() {
1414 if res(index) != ty {
1415 self.error(format!(
1416 "result {} of the signature is {ty} and this call produces {}",
1417 index + 1,
1418 res(index)
1419 ));
1420 }
1421 }
1422 }
1423 if let Some(callee) = info.callee {
1426 if let Some(SymbolRef::Func(id)) = self.module.lookup(callee) {
1427 if self.module[id].signature() != signature {
1428 self.error(format!(
1429 "@{} is declared here with another signature",
1430 self.names.resolve(callee)
1431 ));
1432 }
1433 }
1434 }
1435 }
1436
1437 Opcode::Ctlz | Opcode::Cttz | Opcode::Ctpop | Opcode::Bswap | Opcode::Bitreverse => {
1439 if self.takes(opcode, arity, 1) {
1440 self.integer(opcode, arg(0), 0);
1441 if results == 1 {
1442 self.produces(opcode, res(0), arg(0));
1443 }
1444 }
1445 }
1446
1447 Opcode::SAddOverflow
1449 | Opcode::UAddOverflow
1450 | Opcode::SSubOverflow
1451 | Opcode::USubOverflow
1452 | Opcode::SMulOverflow
1453 | Opcode::UMulOverflow => {
1454 if self.takes(opcode, arity, 2) {
1455 self.integer(opcode, arg(0), 0);
1456 self.agree(opcode, arg(0), arg(1));
1457 if results == 2 {
1458 self.produces(opcode, res(0), arg(0));
1459 self.produces(opcode, res(1), arg(0).with_lane(Type::I1));
1460 }
1461 }
1462 }
1463 Opcode::Expect => {
1464 if self.takes(opcode, arity, 2) {
1465 self.agree(opcode, arg(0), arg(1));
1466 if results == 1 {
1467 self.produces(opcode, res(0), arg(0));
1468 }
1469 }
1470 }
1471
1472 Opcode::SetjmpMarker
1476 | Opcode::LongjmpMarker
1477 | Opcode::InlineAsm
1478 | Opcode::TargetIntrinsic => {}
1479
1480 Opcode::CapOf | Opcode::CapRecover | Opcode::CapLoad => {
1485 if self.takes(opcode, arity, 1) {
1486 self.pointer(opcode, arg(0), 0);
1487 if results == 1 {
1488 self.produces(opcode, res(0), Type::CAP);
1489 }
1490 }
1491 }
1492 Opcode::CapNull => {
1493 if self.takes(opcode, arity, 0) && results == 1 {
1494 self.produces(opcode, res(0), Type::CAP);
1495 }
1496 }
1497 Opcode::CapStore => {
1498 if self.takes(opcode, arity, 2) {
1499 self.pointer(opcode, arg(0), 0);
1500 self.capability(opcode, arg(1), 1);
1501 }
1502 }
1503 Opcode::CapExtent | Opcode::CapExtentBack => {
1504 if self.takes(opcode, arity, 3) {
1510 self.capability(opcode, arg(0), 0);
1511 self.pointer(opcode, arg(1), 1);
1512 self.integer(opcode, arg(2), 2);
1513 if results == 1 {
1514 self.produces(opcode, res(0), arg(2));
1515 }
1516 }
1517 }
1518 Opcode::CapNarrow => {
1519 if self.takes(opcode, arity, 3) {
1520 self.capability(opcode, arg(0), 0);
1521 self.integer(opcode, arg(1), 1);
1522 self.integer(opcode, arg(2), 2);
1523 self.agree(opcode, arg(1), arg(2));
1524 if results == 1 {
1525 self.produces(opcode, res(0), Type::CAP);
1526 }
1527 }
1528 }
1529
1530 Opcode::CheckLive | Opcode::CheckType | Opcode::CheckInit | Opcode::CheckRace => {
1534 if self.takes(opcode, arity, 2) {
1535 self.capability(opcode, arg(0), 0);
1536 self.pointer(opcode, arg(1), 1);
1537 }
1538 }
1539 Opcode::CheckBounds => {
1540 if self.takes_either(opcode, arity, 2, 3) {
1544 self.capability(opcode, arg(0), 0);
1545 self.pointer(opcode, arg(1), 1);
1546 if arity == 3 {
1547 self.integer(opcode, arg(2), 2);
1548 }
1549 }
1550 }
1551 Opcode::CheckDeriv => {
1552 if self.takes(opcode, arity, 4) {
1558 self.capability(opcode, arg(0), 0);
1559 self.pointer(opcode, arg(1), 1);
1560 self.pointer(opcode, arg(2), 2);
1561 self.integer(opcode, arg(3), 3);
1562 }
1563 }
1564
1565 Opcode::CheckRestrictRead | Opcode::CheckRestrictWrite => {
1569 if self.takes(opcode, arity, 1) {
1570 self.pointer(opcode, arg(0), 0);
1571 }
1572 self.names_a_scope(func, inst, opcode);
1573 }
1574
1575 Opcode::RestrictEnter => {
1578 if self.takes(opcode, arity, 1) {
1579 self.pointer(opcode, arg(0), 0);
1580 }
1581 self.names_a_scope(func, inst, opcode);
1582 }
1583 Opcode::RestrictLeave => {
1584 if self.takes(opcode, arity, 1) {
1585 self.pointer(opcode, arg(0), 0);
1586 }
1587 }
1588
1589 Opcode::MetaBegin
1593 | Opcode::MetaEnd
1594 | Opcode::MetaType
1595 | Opcode::MetaInit
1596 | Opcode::MetaTransfer => {
1597 if self.takes(opcode, arity, 2) {
1598 self.pointer(opcode, arg(0), 0);
1599 self.integer(opcode, arg(1), 1);
1600 }
1601 }
1602
1603 Opcode::MetaTypeCopy | Opcode::MetaInitCopy => {
1607 if self.takes(opcode, arity, 3) {
1608 self.pointer(opcode, arg(0), 0);
1609 self.pointer(opcode, arg(1), 1);
1610 self.integer(opcode, arg(2), 2);
1611 }
1612 }
1613
1614 Opcode::SafeRegionBegin | Opcode::SafeRegionEnd => {
1617 self.takes(opcode, arity, 0);
1618 }
1619 }
1620 }
1621
1622 fn names_a_scope(&mut self, func: &'a Func, inst: Inst, opcode: Opcode) {
1631 let Extra::Mem(at) = func[inst].extra else { return };
1632 if func[at].restrict.clique == 0 {
1633 self.error(format!(
1634 "{} is in no restrict scope, so it would ask nothing",
1635 opcode.name()
1636 ));
1637 }
1638 }
1639
1640 fn takes(&mut self, opcode: Opcode, got: usize, want: usize) -> bool {
1642 if got == want {
1643 return true;
1644 }
1645 self.error(format!("{} takes {want} operands and this one has {got}", opcode.name()));
1646 false
1647 }
1648
1649 fn takes_either(&mut self, opcode: Opcode, got: usize, one: usize, other: usize) -> bool {
1651 if got == one || got == other {
1652 return true;
1653 }
1654 self.error(format!(
1655 "{} takes {one} or {other} operands and this one has {got}",
1656 opcode.name()
1657 ));
1658 false
1659 }
1660
1661 fn targets(&mut self, func: &'a Func, inst: Inst, want: usize) {
1663 let got = func.successors(inst).count();
1664 if got != want {
1665 self.error(format!(
1666 "{} branches to {want} blocks and this one to {got}",
1667 func[inst].opcode.name()
1668 ));
1669 }
1670 }
1671
1672 fn integer(&mut self, opcode: Opcode, ty: Type, n: usize) {
1673 if !ty.lane().is_int() {
1674 self.error(format!(
1675 "operand {} of {} is an integer and this one is {ty}",
1676 n + 1,
1677 opcode.name()
1678 ));
1679 }
1680 }
1681
1682 fn floating(&mut self, opcode: Opcode, ty: Type, n: usize) {
1683 if !ty.lane().is_float() {
1684 self.error(format!(
1685 "operand {} of {} is a floating point value and this one is {ty}",
1686 n + 1,
1687 opcode.name()
1688 ));
1689 }
1690 }
1691
1692 fn capability(&mut self, opcode: Opcode, ty: Type, n: usize) {
1693 if !ty.is_cap() {
1694 self.error(format!(
1695 "operand {} of {} is a capability and this one is {ty}",
1696 n + 1,
1697 opcode.name()
1698 ));
1699 }
1700 }
1701
1702 fn pointer(&mut self, opcode: Opcode, ty: Type, n: usize) {
1703 if !ty.is_ptr() {
1704 self.error(format!(
1705 "operand {} of {} is a pointer and this one is {ty}",
1706 n + 1,
1707 opcode.name()
1708 ));
1709 }
1710 }
1711
1712 fn agree(&mut self, opcode: Opcode, first: Type, second: Type) {
1714 if first != second {
1715 self.error(format!(
1716 "the operands of {} have one type and these are {first} and {second}",
1717 opcode.name()
1718 ));
1719 }
1720 }
1721
1722 fn produces(&mut self, opcode: Opcode, got: Type, want: Type) {
1724 if got != want {
1725 self.error(format!(
1726 "{} produces {want} here and this one produces {got}",
1727 opcode.name()
1728 ));
1729 }
1730 }
1731
1732 fn lanes(&mut self, opcode: Opcode, to: Type, from: Type) {
1734 if to.lanes() != from.lanes() {
1735 self.error(format!(
1736 "{} keeps the lane count and {from} has {} and {to} has {}",
1737 opcode.name(),
1738 from.lanes(),
1739 to.lanes()
1740 ));
1741 }
1742 }
1743
1744 fn widens(&mut self, opcode: Opcode, to: Type, from: Type, wider: bool) {
1746 let (a, b) = (to.lane().bits(), from.lane().bits());
1747 let ok = if wider { a > b } else { a < b };
1748 if !ok {
1749 let way = if wider { "wider" } else { "narrower" };
1750 self.error(format!(
1751 "{} produces something {way} and {from} to {to} is not",
1752 opcode.name()
1753 ));
1754 }
1755 }
1756
1757 fn error(&mut self, message: impl Into<String>) {
1760 let at = self.locate();
1761 self.at(at, message);
1762 }
1763
1764 fn at(&mut self, at: String, message: impl Into<String>) {
1765 self.errors.push(VerifyError { at, message: message.into() });
1766 }
1767
1768 fn locate(&self) -> String {
1774 use fmt::Write as _;
1775 let mut at = String::new();
1776 if let Some(func) = self.func {
1777 let _ = write!(at, "@{}", self.names.resolve(func.name));
1778 if let Some(block) = self.block {
1779 let _ = write!(at, " block{}", block.raw());
1780 }
1781 if let Some(inst) = self.inst {
1782 let _ = write!(at, " {}", func[inst].opcode.name());
1783 }
1784 }
1785 at
1786 }
1787}
1788
1789struct Layout {
1792 block: Vec<Option<Block>>,
1793 position: Vec<u32>,
1794}
1795
1796impl Layout {
1797 fn new(func: &Func) -> Self {
1798 let counts = func.counts();
1799 let mut layout =
1800 Layout { block: vec![None; counts.insts], position: vec![0; counts.insts] };
1801 for block in func.blocks() {
1802 for (position, inst) in func.insts(block).enumerate() {
1803 layout.block[inst.index()] = Some(block);
1804 layout.position[inst.index()] = position as u32;
1805 }
1806 }
1807 layout
1808 }
1809
1810 fn block_of(&self, inst: Inst) -> Option<Block> {
1811 self.block[inst.index()]
1812 }
1813
1814 fn position(&self, inst: Inst) -> u32 {
1815 self.position[inst.index()]
1816 }
1817}
1818
1819struct Doms {
1826 rank: Vec<Option<u32>>,
1829 idom: Vec<u32>,
1831}
1832
1833impl Doms {
1834 fn new(func: &Func) -> Self {
1835 let counts = func.counts();
1836 let entry = func.entry().expect("a function with blocks has a first one");
1837
1838 let mut succs: Vec<Vec<Block>> = vec![Vec::new(); counts.blocks];
1846 for block in func.blocks() {
1847 for inst in func.insts(block) {
1848 succs[block.index()].extend(func.successors(inst).map(|call| call.block));
1849 }
1850 }
1851
1852 let mut order = Vec::new();
1855 let mut seen = vec![false; counts.blocks];
1856 let mut stack = vec![(entry, 0usize)];
1857 seen[entry.index()] = true;
1858 while let Some((block, next)) = stack.pop() {
1859 match succs[block.index()].get(next) {
1860 Some(&target) => {
1861 stack.push((block, next + 1));
1862 if !seen[target.index()] {
1863 seen[target.index()] = true;
1864 stack.push((target, 0));
1865 }
1866 }
1867 None => order.push(block),
1868 }
1869 }
1870 order.reverse();
1871
1872 let mut rank = vec![None; counts.blocks];
1873 for (index, &block) in order.iter().enumerate() {
1874 rank[block.index()] = Some(index as u32);
1875 }
1876 let mut preds: Vec<Vec<u32>> = vec![Vec::new(); order.len()];
1877 for (index, &block) in order.iter().enumerate() {
1878 for &target in &succs[block.index()] {
1879 if let Some(target) = rank[target.index()] {
1880 preds[target as usize].push(index as u32);
1881 }
1882 }
1883 }
1884
1885 const NONE: u32 = u32::MAX;
1889 let mut idom = vec![NONE; order.len()];
1890 if !order.is_empty() {
1891 idom[0] = 0;
1892 }
1893 let mut changed = true;
1894 while changed {
1895 changed = false;
1896 for index in 1..order.len() {
1897 let mut new = NONE;
1898 for &pred in &preds[index] {
1899 if idom[pred as usize] == NONE {
1900 continue;
1901 }
1902 new = if new == NONE { pred } else { meet(&idom, new, pred) };
1903 }
1904 if new != NONE && idom[index] != new {
1905 idom[index] = new;
1906 changed = true;
1907 }
1908 }
1909 }
1910 Doms { rank, idom }
1911 }
1912
1913 fn reaches(&self, block: Block) -> bool {
1915 self.rank[block.index()].is_some()
1916 }
1917
1918 fn dominates(&self, of: Block, block: Block) -> bool {
1924 let (Some(a), Some(b)) = (self.rank[of.index()], self.rank[block.index()]) else {
1925 return true;
1926 };
1927 let mut walk = b;
1928 while walk > a {
1929 walk = self.idom[walk as usize];
1930 }
1931 walk == a
1932 }
1933}
1934
1935fn extra_mem(func: &Func, inst: Inst) -> bool {
1940 func[inst].opcode.writes_memory() && func.mem_out(inst).is_some()
1941}
1942
1943fn meet(idom: &[u32], mut a: u32, mut b: u32) -> u32 {
1945 while a != b {
1946 while a > b {
1947 a = idom[a as usize];
1948 }
1949 while b > a {
1950 b = idom[b as usize];
1951 }
1952 }
1953 a
1954}
1955
1956fn width(ty: Type) -> u64 {
1958 u64::from(ty.bits()) * u64::from(ty.lanes())
1959}
1960
1961fn types(list: &[Type]) -> String {
1963 if list.is_empty() {
1964 return "nothing".to_string();
1965 }
1966 list.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ")
1967}
1968
1969#[cfg(test)]
1970mod tests {
1971 use rucc_base::{Idx, Interner, Symbol};
1972 use rucc_diag::Span;
1973 use rucc_target::{Arch, Env, Os, Triple};
1974
1975 use super::*;
1976 use crate::fixtures::{EXAMPLE, SAFETY, SYMBOLS, ZOO};
1977 use crate::func::Builder;
1978 use crate::inst::{InstData, MetaNode, Signature, TbaaNode};
1979 use crate::{Flags, IntPred, parse};
1980
1981 fn target() -> TargetInfo {
1982 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1983 }
1984
1985 fn errors(text: &str) -> Vec<String> {
1988 let mut names = Interner::new();
1989 let module = match parse(text, &mut names) {
1990 Ok(module) => module,
1991 Err(error) => panic!("{error}"),
1992 };
1993 match verify(&module, &names) {
1994 Ok(()) => Vec::new(),
1995 Err(errors) => errors.iter().map(ToString::to_string).collect(),
1996 }
1997 }
1998
1999 fn only(text: &str) -> String {
2001 let found = errors(text);
2002 assert_eq!(found.len(), 1, "{found:#?}");
2003 found.into_iter().next().expect("just counted one")
2004 }
2005
2006 const HEADER: &str = "\
2007; ModuleID = 'bad.c'
2008; format 0
2009target triple = \"x86_64-unknown-linux-gnu\"
2010target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
2011";
2012
2013 fn wrap(signature: &str, body: &str) -> String {
2015 format!("{HEADER}\nfunc @f{signature}, linkage(external) {{\n{body}}}\n")
2016 }
2017
2018 fn reports(text: &str, message: &str) {
2025 let found = errors(text);
2026 assert!(found.iter().any(|error| error.contains(message)), "{message}\nin {found:#?}");
2027 }
2028
2029 #[test]
2030 fn the_four_fixtures_are_modules_the_compiler_may_believe() {
2031 for text in [EXAMPLE, ZOO, SYMBOLS, SAFETY] {
2032 assert_eq!(errors(text), Vec::<String>::new());
2033 }
2034 }
2035
2036 const CHAIN: &str = "block0(%0: ptr):
2038 %1 = mem_entry
2039 %2 = iconst.i32 7
2040 %3 = store %2 -> %0, size 4, align 4 [mem %1]
2041 %4 = load.i32 %0, size 4, align 4 [mem %3]
2042 return %4
2043";
2044
2045 #[test]
2046 fn a_function_carrying_memory_is_one_the_compiler_may_believe() {
2047 assert_eq!(errors(&wrap("(ptr) -> i32", CHAIN)), Vec::<String>::new());
2048 }
2049
2050 #[test]
2051 fn an_instruction_that_touches_no_memory_and_is_on_the_chain_is_reported() {
2052 let text = wrap(
2053 "(ptr) -> i32",
2054 "block0(%0: ptr):
2055 %1 = mem_entry
2056 %2 = iconst.i32 7
2057 %3 = store %2 -> %0, size 4, align 4 [mem %1]
2058 %4 = add %2, %2 [mem %3]
2059 return %4
2060",
2061 );
2062 assert_eq!(
2063 only(&text),
2064 "@f block0 add: add does not touch memory and is on the memory chain"
2065 );
2066 }
2067
2068 #[test]
2069 fn a_write_on_the_chain_at_one_end_only_is_reported() {
2070 let text = wrap(
2071 "(ptr) -> i32",
2072 "block0(%0: ptr):
2073 %1 = mem_entry
2074 %2 = iconst.i32 7
2075 store %2 -> %0, size 4, align 4 [mem %1]
2076 %3 = load.i32 %0, size 4, align 4 [mem %1]
2077 return %3
2078",
2079 );
2080 assert_eq!(
2081 only(&text),
2082 "@f block0 store: store takes memory and produces none, and a write does both or neither"
2083 );
2084 }
2085
2086 #[test]
2087 fn memory_reaching_somewhere_that_is_not_the_memory_operand_is_reported() {
2088 let text = wrap(
2089 "(ptr) -> i32",
2090 "block0(%0: ptr):
2091 %1 = mem_entry
2092 %2 = iconst.i32 7
2093 %3 = store %2 -> %0, size 4, align 4 [mem %1]
2094 %4 = store %1 -> %0, size 8, align 8 [mem %3]
2095 %5 = load.i32 %0, size 4, align 4 [mem %4]
2096 return %5
2097",
2098 );
2099 assert_eq!(
2100 only(&text),
2101 "@f block0 store: %1 is memory and operand 1 of store is not the memory operand"
2102 );
2103 }
2104
2105 #[test]
2106 fn a_memory_phi_is_an_ordinary_block_parameter() {
2107 let text = wrap(
2108 "(ptr, i1) -> i32",
2109 "block0(%0: ptr, %1: i1):
2110 %2 = mem_entry
2111 br_if %1, block1(%2), block2(%2)
2112
2113block1(%3: mem):
2114 %4 = iconst.i32 7
2115 %5 = store %4 -> %0, size 4, align 4 [mem %3]
2116 jump block3(%5)
2117
2118block2(%6: mem):
2119 jump block3(%6)
2120
2121block3(%7: mem):
2122 %8 = load.i32 %0, size 4, align 4 [mem %7]
2123 return %8
2124",
2125 );
2126 assert_eq!(errors(&text), Vec::<String>::new());
2127 }
2128
2129 #[test]
2130 fn a_second_start_to_the_chain_is_reported() {
2131 let text = wrap(
2132 "(ptr) -> i32",
2133 "block0(%0: ptr):
2134 %1 = mem_entry
2135 %2 = mem_entry
2136 %3 = iconst.i32 7
2137 %4 = store %3 -> %0, size 4, align 4 [mem %1]
2138 %5 = load.i32 %0, size 4, align 4 [mem %4]
2139 return %5
2140",
2141 );
2142 assert_eq!(
2143 only(&text),
2144 "@f block0 mem_entry: a function starts with one version of memory and this is a second one"
2145 );
2146 }
2147
2148 #[test]
2149 fn a_chain_with_no_start_is_reported() {
2150 let text = wrap(
2153 "(ptr, mem) -> i32",
2154 "block0(%0: ptr, %1: mem):
2155 %2 = iconst.i32 7
2156 %3 = store %2 -> %0, size 4, align 4 [mem %1]
2157 %4 = load.i32 %0, size 4, align 4 [mem %3]
2158 return %4
2159",
2160 );
2161 assert_eq!(
2162 errors(&text),
2163 [
2164 "@f: parameter 2 is memory, and no function takes or returns memory",
2165 "@f block0 store: this is on the memory chain and the function has no mem_entry",
2166 ]
2167 );
2168 }
2169
2170 #[test]
2171 fn half_a_function_on_the_chain_is_reported() {
2172 let text = wrap(
2173 "(ptr) -> i32",
2174 "block0(%0: ptr):
2175 %1 = mem_entry
2176 %2 = iconst.i32 7
2177 %3 = store %2 -> %0, size 4, align 4 [mem %1]
2178 %4 = load.i32 %0, size 4, align 4
2179 return %4
2180",
2181 );
2182 assert_eq!(
2183 only(&text),
2184 "@f block0 load: load touches memory and is not on the chain, and the rest of this function is"
2185 );
2186 }
2187
2188 #[test]
2189 fn a_chain_that_starts_and_goes_nowhere_is_reported() {
2190 let text = wrap(
2191 "(ptr) -> i32",
2192 "block0(%0: ptr):
2193 %1 = mem_entry
2194 %2 = load.i32 %0, size 4, align 4
2195 return %2
2196",
2197 );
2198 assert_eq!(
2199 only(&text),
2200 "@f block0 mem_entry: nothing in this function is on the memory chain, and this starts one"
2201 );
2202 }
2203
2204 #[test]
2205 fn a_start_to_the_chain_outside_the_entry_block_is_reported() {
2206 let text = wrap(
2207 "(ptr, i1) -> i32",
2208 "block0(%0: ptr, %1: i1):
2209 br_if %1, block1, block2
2210
2211block1:
2212 %2 = mem_entry
2213 %3 = load.i32 %0, size 4, align 4 [mem %2]
2214 return %3
2215
2216block2:
2217 %4 = iconst.i32 0
2218 return %4
2219",
2220 );
2221 assert_eq!(only(&text), "@f block1 mem_entry: mem_entry belongs in the entry block");
2222 }
2223
2224 #[test]
2225 fn a_use_that_its_definition_does_not_reach_is_reported() {
2226 let text = wrap(
2227 "(i1) -> i32",
2228 "block0(%0: i1):
2229 br_if %0, block1, block2
2230
2231block1:
2232 %1 = iconst.i32 7
2233 jump block2
2234
2235block2:
2236 return %1
2237",
2238 );
2239 assert_eq!(
2240 only(&text),
2241 "@f block2 return: %1 is produced in block1 and does not reach here"
2242 );
2243 }
2244
2245 #[test]
2246 fn a_use_before_its_definition_in_the_same_block_is_reported() {
2247 let text = wrap(
2248 "() -> i32",
2249 "block0:
2250 %0 = iconst.i32 1
2251 %1 = add %2, %0
2252 %2 = iconst.i32 2
2253 return %1
2254",
2255 );
2256 assert_eq!(only(&text), "@f block0 add: %2 is produced in block0 and does not reach here");
2257 }
2258
2259 #[test]
2260 fn a_call_that_takes_its_arguments_the_way_the_abi_says_is_believed() {
2261 let text = wrap(
2262 "(ptr sret(24, align 8), ptr byval(16, align 8), i8 zext)",
2263 "block0(%0: ptr, %1: ptr, %2: i8):
2264 return
2265",
2266 );
2267 assert_eq!(errors(&text), Vec::<String>::new());
2268 }
2269
2270 #[test]
2271 fn a_call_that_says_how_an_argument_past_its_parameter_list_travels_is_believed() {
2272 let text = wrap(
2273 "(ptr)",
2274 "block0(%0: ptr):
2275 call @p(%0, %0 byval(24, align 8)) : (ptr, ...)
2276 return
2277",
2278 );
2279 assert_eq!(errors(&text), Vec::<String>::new());
2280 }
2281
2282 #[test]
2283 fn a_call_that_says_it_of_something_that_is_not_a_pointer_is_reported() {
2284 let text = wrap(
2285 "(ptr)",
2286 "block0(%0: ptr):
2287 %1 = iconst.i32 1
2288 call @p(%0, %1 byval(4, align 4)) : (ptr, ...)
2289 return
2290",
2291 );
2292 assert_eq!(
2293 only(&text),
2294 "@f block0 call: argument 2 travels indirectly and i32 is not a pointer"
2295 );
2296 }
2297
2298 #[test]
2299 fn a_call_that_says_an_argument_past_its_parameter_list_is_an_sret_is_reported() {
2300 let text = wrap(
2301 "(ptr)",
2302 "block0(%0: ptr):
2303 call @p(%0, %0 sret(24, align 8)) : (ptr, ...)
2304 return
2305",
2306 );
2307 assert_eq!(
2308 only(&text),
2309 "@f block0 call: argument 2 is an sret and only a parameter can be one"
2310 );
2311 }
2312
2313 #[test]
2314 fn a_call_that_is_not_variadic_says_nothing_about_a_variadic_argument() {
2315 let text = wrap(
2316 "(ptr)",
2317 "block0(%0: ptr):
2318 call @p(%0, %0 byval(24, align 8)) : (ptr)
2319 return
2320",
2321 );
2322 assert_eq!(
2323 errors(&text),
2324 vec![
2325 "@f block0 call: the signature takes 1 and this call passes 2",
2326 "@f block0 call: this call is not variadic and says how a variadic argument \
2327 travels",
2328 ]
2329 );
2330 }
2331
2332 #[test]
2333 fn an_sret_that_is_not_the_first_parameter_is_reported() {
2334 let text = wrap(
2335 "(ptr byval(8, align 8), ptr sret(16, align 8))",
2336 "block0(%0: ptr, %1: ptr):
2337 return
2338",
2339 );
2340 assert_eq!(only(&text), "@f: sret is the first parameter and this one is not");
2341 }
2342
2343 #[test]
2344 fn a_function_returning_through_sret_returns_nothing_else() {
2345 let text = wrap(
2346 "(ptr sret(8, align 8)) -> i32",
2347 "block0(%0: ptr):
2348 %1 = iconst.i32 7
2349 return %1
2350",
2351 );
2352 assert_eq!(only(&text), "@f: a signature returning through sret returns nothing else");
2353 }
2354
2355 #[test]
2356 fn an_object_that_travels_indirectly_travels_behind_a_pointer() {
2357 let text = wrap(
2358 "(i32 byval(4, align 4))",
2359 "block0(%0: i32):
2360 return
2361",
2362 );
2363 assert_eq!(only(&text), "@f: parameter 1 travels indirectly and i32 is not a pointer");
2364 }
2365
2366 #[test]
2367 fn an_alignment_a_parameter_could_not_have_is_reported() {
2368 let text = wrap(
2369 "(ptr byval(24, align 3))",
2370 "block0(%0: ptr):
2371 return
2372",
2373 );
2374 assert_eq!(only(&text), "@f: an alignment is a power of two and this is 3");
2375 }
2376
2377 #[test]
2381 fn a_slot_holding_bytes_the_object_does_not_have_is_reported() {
2382 let text = wrap(
2383 "(ptr) -> ptr",
2384 "block0(%0: ptr):
2385 %1 = va_object %0, size 12, align 8, in(int 8 at 0, int 8 at 8)
2386 return %1
2387",
2388 );
2389 assert_eq!(
2390 only(&text),
2391 "@f block0 va_object: a slot holds bytes 8 to 16 of an object of 12 bytes"
2392 );
2393 }
2394
2395 #[test]
2396 fn a_result_does_not_travel_indirectly() {
2397 let text = wrap(
2400 "() -> ptr byval(16, align 8)",
2401 "block0:
2402 %0 = iconst.i64 0
2403 %1 = inttoptr.ptr %0
2404 return %1
2405",
2406 );
2407 assert_eq!(only(&text), "@f: result 1 travels indirectly and a result cannot");
2408 }
2409
2410 #[test]
2411 fn an_extension_is_asked_of_an_integer_and_not_of_anything_else() {
2412 let text = wrap(
2413 "(ptr zext)",
2414 "block0(%0: ptr):
2415 return
2416",
2417 );
2418 assert_eq!(only(&text), "@f: parameter 1 is extended and ptr is not an integer");
2419 }
2420
2421 #[test]
2422 fn a_branch_that_passes_the_wrong_number_of_arguments_is_reported() {
2423 let text = wrap(
2424 "(i32)",
2425 "block0(%0: i32):
2426 jump block1(%0)
2427
2428block1:
2429 return
2430",
2431 );
2432 assert_eq!(
2433 only(&text),
2434 "@f block0 jump: block1 takes 0 arguments and this branch passes 1"
2435 );
2436 }
2437
2438 #[test]
2439 fn a_branch_that_passes_the_wrong_type_is_reported() {
2440 let text = wrap(
2441 "(i32) -> i32",
2442 "block0(%0: i32):
2443 %1 = sext.i64 %0
2444 jump block1(%1)
2445
2446block1(%2: i32):
2447 return %2
2448",
2449 );
2450 assert_eq!(only(&text), "@f block0 jump: argument 1 to block1 is i32 and this one is i64");
2451 }
2452
2453 #[test]
2454 fn a_block_that_does_not_end_in_a_terminator_is_reported() {
2455 let text = wrap("()", "block0:\n %0 = iconst.i32 1\n");
2456 assert_eq!(only(&text), "@f block0: this block does not end in a terminator");
2457 }
2458
2459 #[test]
2460 fn an_instruction_after_the_terminator_is_reported() {
2461 let text = wrap("()", "block0:\n return\n %0 = iconst.i32 1\n");
2462 assert_eq!(only(&text), "@f block0 iconst: this comes after the block's terminator");
2463 }
2464
2465 #[test]
2466 fn an_unreachable_block_is_reported() {
2467 let text = wrap("()", "block0:\n return\n\nblock1:\n return\n");
2468 assert_eq!(only(&text), "@f block1: this block is not reachable and has not been deleted");
2469 }
2470
2471 #[test]
2472 fn a_block_a_jump_to_an_address_arrives_at_is_an_ordinary_target() {
2473 let text = wrap(
2474 "(ptr) -> i32",
2475 "block0(%0: ptr):
2476 %1 = block_addr block1
2477 indirect_br %0, block1
2478
2479block1:
2480 %2 = iconst.i32 1
2481 return %2
2482",
2483 );
2484 assert_eq!(errors(&text), Vec::<String>::new());
2485 }
2486
2487 #[test]
2488 fn a_block_whose_address_is_taken_is_reached_by_the_taking_of_it() {
2489 let text = wrap(
2492 "() -> ptr",
2493 "block0:
2494 %0 = block_addr block1
2495 return %0
2496
2497block1:
2498 unreachable
2499",
2500 );
2501 assert_eq!(errors(&text), Vec::<String>::new());
2502 }
2503
2504 #[test]
2505 fn taking_the_address_of_a_block_and_passing_it_arguments_is_reported() {
2506 let text = wrap(
2509 "(i32) -> ptr",
2510 "block0(%0: i32):
2511 %1 = block_addr block1(%0)
2512 return %1
2513
2514block1(%2: i32):
2515 unreachable
2516",
2517 );
2518 assert_eq!(
2519 only(&text),
2520 "@f block0 block_addr: block_addr names a block and passes it arguments"
2521 );
2522 }
2523
2524 #[test]
2525 fn a_jump_to_something_that_is_not_an_address_is_reported() {
2526 let text = wrap(
2527 "(i32)",
2528 "block0(%0: i32):
2529 indirect_br %0, block1
2530
2531block1:
2532 return
2533",
2534 );
2535 assert_eq!(
2536 only(&text),
2537 "@f block0 indirect_br: operand 1 of indirect_br is a pointer and this one is i32"
2538 );
2539 }
2540
2541 #[test]
2542 fn a_branch_back_to_the_entry_block_is_reported() {
2543 let text = wrap(
2546 "(i32)",
2547 "block0(%0: i32):
2548 jump block1
2549
2550block1:
2551 jump block0(%0)
2552",
2553 );
2554 assert_eq!(
2555 only(&text),
2556 "@f block0: the entry block is branched to, and it takes the arguments"
2557 );
2558 }
2559
2560 #[test]
2561 fn an_entry_block_that_does_not_take_the_arguments_is_reported() {
2562 let text = wrap("(i32)", "block0(%0: i64):\n return\n");
2563 assert_eq!(only(&text), "@f: the entry block takes i64 and the signature says i32");
2564 }
2565
2566 #[test]
2567 fn a_flag_the_opcode_does_not_read_is_reported() {
2568 let text =
2569 wrap("(i32) -> i32", "block0(%0: i32):\n %1 = add.exact %0, %0\n return %1\n");
2570 assert_eq!(only(&text), "@f block0 add: add does not read `exact`");
2571 }
2572
2573 #[test]
2574 fn an_ordering_the_operation_cannot_be_asked_for_is_reported() {
2575 let text = wrap(
2576 "(ptr) -> i32",
2577 "block0(%0: ptr):\n %1 = atomic_load.i32 %0, align 4, release\n return %1\n",
2578 );
2579 assert_eq!(only(&text), "@f block0 atomic_load: atomic_load cannot be asked for release");
2580 }
2581
2582 #[test]
2583 fn an_ordering_on_the_non_atomic_form_is_reported() {
2584 let text = wrap(
2585 "(ptr) -> i32",
2586 "block0(%0: ptr):\n %1 = load.i32 %0, align 4, acquire\n return %1\n",
2587 );
2588 assert_eq!(only(&text), "@f block0 load: load cannot be asked for acquire");
2589 }
2590
2591 #[test]
2592 fn a_restrict_base_outside_any_clique_is_reported() {
2593 let text = wrap(
2594 "(ptr) -> i32",
2595 "block0(%0: ptr):\n %1 = load.i32 %0, align 4, restrict(0, 2)\n return %1\n",
2596 );
2597 assert_eq!(
2598 only(&text),
2599 "@f block0 load: restrict base 2 is in no clique, and a base means nothing without one"
2600 );
2601 }
2602
2603 #[test]
2604 fn padding_owned_by_something_that_is_not_a_store_is_reported() {
2605 let text = wrap(
2609 "(ptr) -> i32",
2610 "block0(%0: ptr):\n %1 = load.i32 %0, align 4, owns 8\n return %1\n",
2611 );
2612 assert_eq!(
2613 only(&text),
2614 "@f block0 load: a load owns no padding, since only a store records any"
2615 );
2616 }
2617
2618 #[test]
2619 fn a_va_object_that_answers_anything_but_an_address_is_reported() {
2620 let text = wrap(
2623 "(ptr) -> i64",
2624 "block0(%0: ptr):
2625 %1 = va_object.i64 %0, size 16, align 8
2626 return %1
2627",
2628 );
2629 assert_eq!(
2630 only(&text),
2631 "@f block0 va_object: va_object answers where the object is and i64 is not an address"
2632 );
2633 }
2634
2635 #[test]
2636 fn an_alloca_of_a_fixed_size_outside_the_entry_block_is_reported() {
2637 let text = wrap(
2638 "()",
2639 "block0:
2640 jump block1
2641
2642block1:
2643 %0 = alloca, size 16, align 8
2644 return
2645",
2646 );
2647 assert_eq!(
2648 only(&text),
2649 "@f block1 alloca: an alloca of a fixed size belongs in the entry block"
2650 );
2651 }
2652
2653 #[test]
2654 fn a_dynamic_alloca_may_be_anywhere() {
2655 let text = wrap(
2656 "(i64)",
2657 "block0(%0: i64):
2658 jump block1
2659
2660block1:
2661 %1 = alloca %0, align 8
2662 return
2663",
2664 );
2665 assert_eq!(errors(&text), Vec::<String>::new());
2666 }
2667
2668 #[test]
2669 fn two_cases_of_a_switch_with_the_same_value_are_reported() {
2670 let text = wrap(
2671 "(i32)",
2672 "block0(%0: i32):
2673 switch %0, block1, [7 => block1, 7 => block1]
2674
2675block1:
2676 return
2677",
2678 );
2679 assert_eq!(only(&text), "@f block0 switch: two cases of this switch have the same value");
2680 }
2681
2682 #[test]
2683 fn operands_that_do_not_agree_are_reported() {
2684 let text = wrap(
2685 "(i32, i64) -> i32",
2686 "block0(%0: i32, %1: i64):\n %2 = add %0, %1\n return %2\n",
2687 );
2688 assert_eq!(
2689 only(&text),
2690 "@f block0 add: the operands of add have one type and these are i32 and i64"
2691 );
2692 }
2693
2694 #[test]
2695 fn a_conversion_that_goes_the_wrong_way_is_reported() {
2696 let text = wrap("(i32) -> i64", "block0(%0: i32):\n %1 = trunc.i64 %0\n return %1\n");
2697 assert_eq!(
2698 only(&text),
2699 "@f block0 trunc: trunc produces something narrower and i32 to i64 is not"
2700 );
2701 }
2702
2703 #[test]
2704 fn a_bitcast_between_an_address_and_a_number_is_reported() {
2705 let text =
2706 wrap("(ptr) -> i64", "block0(%0: ptr):\n %1 = bitcast.i64 %0\n return %1\n");
2707 assert_eq!(
2708 only(&text),
2709 "@f block0 bitcast: a bitcast between a pointer and a number is ptrtoint or inttoptr"
2710 );
2711 }
2712
2713 #[test]
2714 fn an_operand_of_the_wrong_kind_is_reported() {
2715 let text = wrap("(i32) -> i32", "block0(%0: i32):\n %1 = fadd %0, %0\n return %1\n");
2716 assert_eq!(
2717 only(&text),
2718 "@f block0 fadd: operand 1 of fadd is a floating point value and this one is i32"
2719 );
2720 }
2721
2722 #[test]
2723 fn a_condition_that_is_not_one_bit_is_reported() {
2724 let text = wrap(
2725 "(i32)",
2726 "block0(%0: i32):
2727 br_if %0, block1, block1
2728
2729block1:
2730 return
2731",
2732 );
2733 assert_eq!(only(&text), "@f block0 br_if: br_if branches on an i1 and this one on i32");
2734 }
2735
2736 #[test]
2737 fn a_return_that_does_not_match_the_signature_is_reported() {
2738 let text = wrap("() -> i32", "block0:\n return\n");
2739 assert_eq!(only(&text), "@f block0 return: the signature returns 1 and this returns 0");
2740 }
2741
2742 #[test]
2743 fn a_call_that_disagrees_with_the_declaration_is_reported() {
2744 let text = format!(
2745 "{HEADER}
2746func @g(i32, ...) -> i32, linkage(external);
2747
2748func @f(i32) -> i32, linkage(external) {{
2749block0(%0: i32):
2750 %1 = call @g(%0) : (i32) -> i32
2751 return %1
2752}}
2753"
2754 );
2755 assert_eq!(only(&text), "@f block0 call: @g is declared here with another signature");
2756 }
2757
2758 #[test]
2759 fn a_global_whose_image_is_not_its_size_is_reported() {
2760 let text =
2761 format!("{HEADER}\nglobal @x : bytes 8 = {{ i32 7 }}, align 4, linkage(external)\n");
2762 assert_eq!(only(&text), "@x: the image is 4 bytes and the global is 8");
2763 }
2764
2765 #[test]
2766 fn a_pointer_in_an_image_has_no_width_and_is_reported() {
2767 let text = format!(
2771 "{HEADER}\nglobal @x : bytes 8 = {{ ptr 0x0, zero 8 }}, align 8, linkage(external)\n"
2772 );
2773 assert_eq!(only(&text), "@x: a scalar in an image has a width and ptr has none");
2774 }
2775
2776 #[test]
2777 fn a_declaration_of_something_another_module_defines_may_be_constant() {
2778 let text = format!("{HEADER}\nglobal @x : bytes 4, align 4, linkage(external), constant\n");
2781 assert!(errors(&text).is_empty(), "{:?}", errors(&text));
2782 }
2783
2784 #[test]
2785 fn an_alias_to_itself_is_reported() {
2786 let text = format!("{HEADER}\nalias @a = @a, linkage(external)\n");
2787 assert_eq!(only(&text), "@a: an alias to itself");
2788 }
2789
2790 #[test]
2791 fn an_ifunc_that_does_not_resolve_through_a_function_is_reported() {
2792 let text = format!(
2793 "{HEADER}
2794global @g : i32 = 0, align 4, linkage(external)
2795
2796ifunc @f = @g, linkage(external)
2797"
2798 );
2799 assert_eq!(
2800 only(&text),
2801 "@f: an ifunc resolves through a function and this target is not one"
2802 );
2803 }
2804
2805 #[test]
2806 fn attributes_that_contradict_each_other_are_reported() {
2807 let text =
2808 format!("{HEADER}\nfunc @f(), linkage(external), attrs(always_inline, noinline);\n");
2809 assert_eq!(
2810 only(&text),
2811 "@f: `always_inline` and `noinline` cannot both be true of a function"
2812 );
2813 }
2814
2815 fn one_error(module: &Module, func: &Func, names: &Interner) -> String {
2819 match verify_func(module, func, names) {
2820 Ok(()) => panic!("that was expected to be turned down"),
2821 Err(errors) => {
2822 assert_eq!(errors.len(), 1, "{errors:#?}");
2823 errors[0].to_string()
2824 }
2825 }
2826 }
2827
2828 #[test]
2829 fn a_value_the_function_does_not_have_is_reported() {
2830 let mut names = Interner::new();
2831 let module = Module::new(names.intern("built.c"), &target());
2832 let mut func = Func::new(names.intern("f"), Signature::new());
2833 let block = func.create_block();
2834 let args = func.push_values(&[Value::from_usize(9)]);
2835 let inst =
2836 func.create_inst(InstData { args, ..InstData::new(Opcode::Return) }, &[], Span::DUMMY);
2837 func.append_inst(block, inst);
2838 assert_eq!(
2839 one_error(&module, &func, &names),
2840 "@f block0 return: %9 is not a value of this function"
2841 );
2842 }
2843
2844 #[test]
2845 fn a_value_whose_definition_has_been_taken_out_is_reported() {
2846 let mut names = Interner::new();
2847 let module = Module::new(names.intern("built.c"), &target());
2848 let i32_ = Type::int(32);
2849 let mut func = Func::new(names.intern("f"), Signature::new().with_returns(&[i32_]));
2850 let block = func.create_block();
2851 let mut b = Builder::new(&mut func, block);
2852 let value = b.iconst(i32_, 7);
2853 b.ret(&[value]);
2854 let Def::Result { inst, .. } = func[value].def else { unreachable!("a constant") };
2855 func.remove_inst(inst);
2856 assert_eq!(
2857 one_error(&module, &func, &names),
2858 "@f block0 return: %0 is produced by an instruction that is not in the function"
2859 );
2860 }
2861
2862 #[test]
2863 fn an_instruction_carrying_another_opcodes_payload_is_reported() {
2864 let mut names = Interner::new();
2867 let module = Module::new(names.intern("built.c"), &target());
2868 let mut func = Func::new(names.intern("f"), Signature::new().with_params(&[Type::int(32)]));
2869 let block = func.create_block();
2870 let param = func.append_param(block, Type::int(32));
2871 let args = func.push_values(&[param, param]);
2872 let inst = func.create_inst(
2873 InstData { args, extra: Extra::IntPred(IntPred::Eq), ..InstData::new(Opcode::Add) },
2874 &[Type::int(32)],
2875 Span::DUMMY,
2876 );
2877 func.append_inst(block, inst);
2878 let ret = func.create_inst(InstData::new(Opcode::Return), &[], Span::DUMMY);
2879 func.append_inst(block, ret);
2880 assert_eq!(
2881 one_error(&module, &func, &names),
2882 "@f block0 add: add carries nothing and this one carries an integer comparison"
2883 );
2884 }
2885
2886 #[test]
2887 fn a_metadata_node_that_is_its_own_parent_is_reported() {
2888 let mut names = Interner::new();
2889 let mut module = Module::new(names.intern("built.c"), &target());
2890 module.add_meta(MetaNode::Tbaa(TbaaNode {
2891 name: names.intern("int"),
2892 parent: Some(Idx::from_usize(0)),
2893 offset: 0,
2894 }));
2895 let found = match verify(&module, &names) {
2896 Ok(()) => panic!("that was expected to be turned down"),
2897 Err(errors) => errors,
2898 };
2899 assert_eq!(found.len(), 1, "{found:#?}");
2900 assert_eq!(
2901 found[0].to_string(),
2902 "!0: a metadata node names one that comes before it and this one is !0"
2903 );
2904 }
2905
2906 #[test]
2907 fn a_datalayout_the_target_does_not_imply_is_reported() {
2908 let mut names = Interner::new();
2909 let mut module = Module::new(names.intern("built.c"), &target());
2910 module.datalayout = DataLayout::parse("e-p:32:32-i64:64-f80:32-S64").expect("a layout");
2911 let found = match verify(&module, &names) {
2912 Ok(()) => panic!("that was expected to be turned down"),
2913 Err(errors) => errors,
2914 };
2915 assert_eq!(found.len(), 1, "{found:#?}");
2916 assert!(found[0].to_string().starts_with("@built.c: the datalayout is "), "{}", found[0]);
2917 }
2918
2919 #[test]
2920 fn a_flag_riding_along_where_it_is_read_is_not_reported() {
2921 let mut names = Interner::new();
2922 let module = Module::new(names.intern("built.c"), &target());
2923 let i32_ = Type::int(32);
2924 let mut func = Func::new(
2925 names.intern("f"),
2926 Signature::new().with_params(&[i32_]).with_returns(&[i32_]),
2927 );
2928 let block = func.create_block();
2929 let param = func.append_param(block, i32_);
2930 let mut b = Builder::new(&mut func, block);
2931 let sum = b.binary(Opcode::Add, param, param, Flags::NSW);
2932 b.ret(&[sum]);
2933 assert!(verify_func(&module, &func, &names).is_ok());
2934 }
2935
2936 #[test]
2937 fn a_declaration_is_checked_and_has_nothing_else_to_check() {
2938 let mut names = Interner::new();
2939 let module = Module::new(names.intern("built.c"), &target());
2940 let func = Func::new(Symbol::from_raw(0), Signature::new());
2941 assert!(func.is_declaration());
2942 assert!(verify_func(&module, &func, &names).is_ok());
2943 }
2944
2945 #[test]
2950 fn a_capability_parameter_is_reported() {
2951 let text = wrap(
2952 "(cap) -> i32",
2953 "block0(%0: cap):
2954 %1 = iconst.i32 0
2955 return %1
2956",
2957 );
2958 reports(&text, "parameter 1 is a capability and a capability does not cross a call");
2959 }
2960
2961 #[test]
2962 fn a_capability_result_is_reported() {
2963 let text = wrap(
2964 "(i32) -> cap",
2965 "block0(%0: i32):
2966 unreachable
2967",
2968 );
2969 reports(&text, "result 1 is a capability and a capability does not cross a call");
2970 }
2971
2972 #[test]
2973 fn a_capability_a_block_takes_is_a_capability_like_any_other() {
2974 let text = wrap(
2977 "(i1, ptr) -> i32",
2978 "block0(%0: i1, %1: ptr):
2979 %2 = cap_of %1
2980 br_if %0, block1(%2), block1(%2)
2981
2982block1(%3: cap):
2983 %4 = iconst.i32 0
2984 return %4
2985",
2986 );
2987 assert_eq!(errors(&text), Vec::<String>::new());
2988 }
2989
2990 #[test]
2991 fn an_instruction_that_is_not_a_cap_instruction_producing_one_is_reported() {
2992 let text = wrap(
2993 "(ptr) -> i32",
2994 "block0(%0: ptr):
2995 %1 = cap_of %0
2996 %2 = bitcast.cap %1
2997 %3 = iconst.i32 0
2998 return %3
2999",
3000 );
3001 reports(&text, "bitcast produces a capability and only the cap instructions do that");
3002 }
3003
3004 #[test]
3005 fn a_capability_where_a_pointer_belongs_is_reported() {
3006 let text = wrap(
3007 "(ptr) -> i32",
3008 "block0(%0: ptr):
3009 %1 = cap_of %0
3010 %2 = cap_store %1, %1
3011 %3 = iconst.i32 0
3012 return %3
3013",
3014 );
3015 reports(&text, "operand 1 of cap_store is a pointer and this one is cap");
3016 }
3017
3018 #[test]
3019 fn a_pointer_where_a_capability_belongs_is_reported() {
3020 let text = wrap(
3021 "(ptr) -> i32",
3022 "block0(%0: ptr):
3023 %1 = cap_store %0, %0
3024 %2 = iconst.i32 0
3025 return %2
3026",
3027 );
3028 reports(&text, "operand 2 of cap_store is a capability and this one is ptr");
3029 }
3030
3031 #[test]
3032 fn narrowing_by_an_offset_and_a_length_of_different_widths_is_reported() {
3033 let text = wrap(
3034 "(ptr, i64) -> i32",
3035 "block0(%0: ptr, %1: i64):
3036 %2 = cap_of %0
3037 %3 = iconst.i32 4
3038 %4 = cap_narrow %2, %1, %3
3039 %5 = iconst.i32 0
3040 return %5
3041",
3042 );
3043 reports(&text, "the operands of cap_narrow have one type and these are i64 and i32");
3044 }
3045
3046 #[test]
3047 fn an_extent_answered_in_a_different_width_from_the_limit_is_reported() {
3048 let text = wrap(
3051 "(ptr, i64) -> i32",
3052 "block0(%0: ptr, %1: i64):
3053 %2 = cap_of %0
3054 %3 = cap_extent.i32 %2, %0, %1
3055 return %3
3056",
3057 );
3058 reports(&text, "cap_extent produces i64 here and this one produces i32");
3059 }
3060
3061 #[test]
3062 fn a_capability_instruction_with_the_wrong_number_of_operands_is_reported() {
3063 let text = wrap(
3064 "(ptr) -> i32",
3065 "block0(%0: ptr):
3066 %1 = cap_of %0, %0
3067 %2 = iconst.i32 0
3068 return %2
3069",
3070 );
3071 reports(&text, "cap_of takes 1 operands and this one has 2");
3072 }
3073
3074 #[test]
3075 fn a_check_given_its_pointer_and_its_capability_the_other_way_round_is_reported() {
3076 let text = wrap(
3077 "(ptr) -> i32",
3078 "block0(%0: ptr):
3079 %1 = cap_of %0
3080 check_live %0, %1
3081 %2 = iconst.i32 0
3082 return %2
3083",
3084 );
3085 reports(&text, "operand 1 of check_live is a capability and this one is ptr");
3086 reports(&text, "operand 2 of check_live is a pointer and this one is cap");
3087 }
3088
3089 #[test]
3090 fn a_check_on_the_same_value_twice_is_reported() {
3091 let text = wrap(
3094 "(ptr) -> i32",
3095 "block0(%0: ptr):
3096 check_bounds %0, %0, size 4, align 4
3097 %1 = iconst.i32 0
3098 return %1
3099",
3100 );
3101 reports(&text, "operand 1 of check_bounds is a capability and this one is ptr");
3102 }
3103
3104 #[test]
3105 fn a_bounds_check_over_a_length_that_is_not_a_number_is_reported() {
3106 let text = wrap(
3109 "(ptr) -> i32",
3110 "block0(%0: ptr):
3111 %1 = cap_of %0
3112 check_bounds %1, %0, %0, size 4, align 4
3113 %2 = iconst.i32 0
3114 return %2
3115",
3116 );
3117 reports(&text, "operand 3 of check_bounds is an integer and this one is ptr");
3118 }
3119
3120 #[test]
3121 fn a_bounds_check_with_a_fourth_operand_is_reported() {
3122 let text = wrap(
3123 "(ptr) -> i32",
3124 "block0(%0: ptr):
3125 %1 = cap_of %0
3126 %2 = iconst.i64 4
3127 check_bounds %1, %0, %2, %2, size 4, align 4
3128 %3 = iconst.i32 0
3129 return %3
3130",
3131 );
3132 reports(&text, "check_bounds takes 2 or 3 operands and this one has 4");
3133 }
3134
3135 #[test]
3136 fn a_derivation_check_without_the_pointer_it_is_about_is_reported() {
3137 let text = wrap(
3138 "(ptr) -> i32",
3139 "block0(%0: ptr):
3140 %1 = cap_of %0
3141 %2 = iconst.i64 4
3142 check_deriv %1, %0, %2
3143 %3 = iconst.i32 0
3144 return %3
3145",
3146 );
3147 reports(&text, "check_deriv takes 4 operands and this one has 3");
3148 }
3149
3150 #[test]
3151 fn a_plane_write_over_a_length_that_is_not_a_number_is_reported() {
3152 let text = wrap(
3155 "(ptr) -> i32",
3156 "block0(%0: ptr):
3157 meta_end %0, %0
3158 %1 = iconst.i32 0
3159 return %1
3160",
3161 );
3162 reports(&text, "operand 2 of meta_end is an integer and this one is ptr");
3163 }
3164
3165 #[test]
3166 fn a_region_marker_handed_a_value_is_reported() {
3167 let text = wrap(
3170 "(ptr) -> i32",
3171 "block0(%0: ptr):
3172 safe_region_end %0
3173 %1 = iconst.i32 0
3174 return %1
3175",
3176 );
3177 reports(&text, "safe_region_end takes 0 operands and this one has 1");
3178 }
3179
3180 #[test]
3181 fn a_fact_about_something_that_is_not_a_pointer_is_reported() {
3182 let text = wrap(
3185 "(i32) -> i32",
3186 "block0(%0: i32):
3187 return %0
3188
3189facts:
3190 %0 = !live
3191",
3192 );
3193 reports(&text, "%0 is said to be a pointer and it is i32");
3194 }
3195
3196 #[test]
3197 fn an_alignment_that_is_not_a_power_of_two_is_reported() {
3198 let text = wrap(
3201 "(ptr) -> ptr",
3202 "block0(%0: ptr):
3203 return %0
3204
3205facts:
3206 %0 = !aligned(6)
3207",
3208 );
3209 reports(&text, "%0 is said to be aligned to 6 and an alignment is a power of two");
3210 }
3211
3212 #[test]
3213 fn a_range_that_starts_somewhere_that_is_not_a_pointer_is_reported() {
3214 let text = wrap(
3217 "(ptr, i64) -> ptr",
3218 "block0(%0: ptr, %1: i64):
3219 return %0
3220
3221facts:
3222 %0 = !bounds(%1, %1)
3223",
3224 );
3225 reports(&text, "the range %0 is in starts at a pointer and %1 is i64");
3226 }
3227
3228 #[test]
3229 fn a_range_whose_extent_is_not_a_number_is_reported() {
3230 let text = wrap(
3231 "(ptr) -> ptr",
3232 "block0(%0: ptr):
3233 return %0
3234
3235facts:
3236 %0 = !bounds(%0, %0)
3237",
3238 );
3239 reports(&text, "the range %0 is in has an integer extent and %0 is ptr");
3240 }
3241
3242 #[test]
3243 fn a_range_computed_after_the_pointer_it_is_about_is_reported() {
3244 let text = wrap(
3247 "(ptr, i64) -> ptr",
3248 "block0(%0: ptr, %1: i64):
3249 %2 = ptr_add %0, %1
3250 %3 = iconst.i64 16
3251 return %2
3252
3253facts:
3254 %2 = !bounds(%0, %3)
3255",
3256 );
3257 reports(&text, "%2 names %3 and does not reach it");
3258 }
3259
3260 #[test]
3261 fn a_load_handed_a_plane_entry_is_reported() {
3262 let text = format!(
3265 "{HEADER}\nfunc @f(ptr) -> i32, linkage(external) {{\n\
3266 block0(%0: ptr):\n %1 = load.i32 %0, align 4, tbaa !0\n return %1\n}}\n\
3267 \n!0 = plane character\n"
3268 );
3269 reports(&text, "load names an aliasing node and !0 is a plane entry");
3270 }
3271
3272 #[test]
3273 fn a_type_check_handed_an_aliasing_node_is_reported() {
3274 let text = format!(
3275 "{HEADER}\nfunc @f(ptr) -> i32, linkage(external) {{\n\
3276 block0(%0: ptr):\n %1 = cap_of %0\n \
3277 check_type %1, %0, size 4, align 4, tbaa !0\n %2 = iconst.i32 0\n \
3278 return %2\n}}\n\n!0 = tbaa \"int\", offset 0\n"
3279 );
3280 reports(&text, "check_type names a plane entry and !0 is an aliasing node");
3281 }
3282
3283 #[test]
3284 fn a_plane_entry_for_a_byte_no_pointer_has_is_reported() {
3285 let text = format!("{HEADER}\n!0 = plane pointer_slot 9\n");
3288 reports(&text, "a pointer on this target is 8 bytes and this is byte 9");
3289 }
3290
3291 #[test]
3292 fn a_plane_entry_naming_another_plane_entry_is_reported() {
3293 let text = format!("{HEADER}\n!0 = plane character\n!1 = plane !0\n");
3294 reports(&text, "a plane entry names a type and !0 is a plane entry");
3295 }
3296
3297 #[test]
3298 fn a_null_capability_handed_an_operand_is_reported() {
3299 let text = wrap(
3302 "(ptr) -> i32",
3303 "block0(%0: ptr):
3304 %1 = cap_null %0
3305 %2 = iconst.i32 0
3306 return %2
3307",
3308 );
3309 reports(&text, "cap_null takes 0 operands and this one has 1");
3310 }
3311}