1use crate::value::{LocalBlockId, LocalValueId, function::FunctionId};
2
3use super::mnemonic::{Args, MnemonicKind};
4use smallvec::{SmallVec, smallvec};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
9pub enum Callee {
10 Real(FunctionId),
11 Minted(u32),
12}
13
14impl Callee {
15 pub const fn real(self) -> Option<FunctionId> {
16 match self {
17 Self::Real(id) => Some(id),
18 Self::Minted(_) => None,
19 }
20 }
21
22 pub const fn minted(self) -> Option<u32> {
23 match self {
24 Self::Real(_) => None,
25 Self::Minted(slot) => Some(slot),
26 }
27 }
28
29 pub fn expect_real(self, operation: &str) -> FunctionId {
31 match self {
32 Self::Real(id) => id,
33 Self::Minted(slot) => {
34 panic!("{operation} requires a real callee; minted placeholder #{slot} escaped")
35 }
36 }
37 }
38}
39
40impl From<FunctionId> for Callee {
41 fn from(id: FunctionId) -> Self {
42 Self::Real(id)
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
47pub struct Branch {
48 pub target: LocalBlockId,
53 pub args: Vec<LocalValueId>,
55}
56
57impl MnemonicKind for Branch {
58 fn opcode(&self) -> &'static str {
59 "branch"
60 }
61
62 fn is_terminator(&self) -> bool {
63 true
64 }
65
66 fn args(&self) -> Args {
67 SmallVec::from_vec(self.args.clone())
68 }
69}
70
71#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
74pub struct SwitchArm {
75 pub value: u64,
77 pub target: LocalBlockId,
80 pub args: Vec<LocalValueId>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
100pub struct Switch {
101 pub scrutinee: LocalValueId,
103 pub cases: Vec<SwitchArm>,
105 pub default: Option<LocalBlockId>,
107 pub default_args: Vec<LocalValueId>,
109}
110
111impl MnemonicKind for Switch {
112 fn opcode(&self) -> &'static str {
113 "switch"
114 }
115
116 fn is_terminator(&self) -> bool {
117 true
118 }
119
120 fn args(&self) -> Args {
121 let mut args = smallvec![self.scrutinee];
122 for case in &self.cases {
123 args.extend_from_slice(&case.args);
124 }
125 args.extend_from_slice(&self.default_args);
126 args
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
131pub struct BranchInd {
132 pub ptr: LocalValueId,
133}
134
135impl MnemonicKind for BranchInd {
136 fn opcode(&self) -> &'static str {
137 "branchind"
138 }
139
140 fn is_terminator(&self) -> bool {
141 true
142 }
143
144 fn args(&self) -> Args {
145 smallvec![self.ptr]
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
159pub struct TailCall {
160 pub target: Callee,
161 pub args: Vec<LocalValueId>,
164}
165
166impl MnemonicKind for TailCall {
167 fn opcode(&self) -> &'static str {
168 "tailcall"
169 }
170
171 fn is_terminator(&self) -> bool {
172 true
173 }
174
175 fn args(&self) -> Args {
176 SmallVec::from_vec(self.args.clone())
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
181pub struct Apply {
182 pub target: Callee,
183 pub args: Vec<LocalValueId>,
185}
186
187impl MnemonicKind for Apply {
188 fn opcode(&self) -> &'static str {
189 "apply"
190 }
191
192 fn args(&self) -> Args {
193 SmallVec::from_vec(self.args.clone())
194 }
195}
196
197#[derive(
205 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
206)]
207pub enum CallTag {
208 #[default]
213 Opaque,
214 RegPure,
219 Pure,
224}
225
226impl CallTag {
227 pub fn is_regpure(self) -> bool {
230 matches!(self, CallTag::RegPure | CallTag::Pure)
231 }
232
233 pub fn is_pure(self) -> bool {
235 matches!(self, CallTag::Pure)
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
240pub struct Call {
241 pub target: Callee,
242 pub args: Vec<LocalValueId>,
244 pub clobbers: Vec<LocalValueId>,
249 #[serde(default)]
252 pub tag: CallTag,
253}
254
255impl MnemonicKind for Call {
256 fn opcode(&self) -> &'static str {
257 "call"
258 }
259
260 fn is_terminator(&self) -> bool {
261 true
262 }
263
264 fn args(&self) -> Args {
265 SmallVec::from_vec(self.args.clone())
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
270pub struct CallInd {
271 pub ptr: LocalValueId,
272 pub args: Vec<LocalValueId>,
273}
274
275impl MnemonicKind for CallInd {
276 fn opcode(&self) -> &'static str {
277 "callind"
278 }
279
280 fn is_terminator(&self) -> bool {
281 true
282 }
283
284 fn args(&self) -> Args {
285 let mut args = smallvec![self.ptr];
286 args.extend(self.args.clone());
287 args
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
292pub struct CBranch {
293 pub condition: LocalValueId,
294 pub success_block: LocalBlockId,
297 pub success_args: Vec<LocalValueId>,
299 pub failure_block: LocalBlockId,
302 pub failure_args: Vec<LocalValueId>,
304}
305
306impl MnemonicKind for CBranch {
307 fn opcode(&self) -> &'static str {
308 "cbranch"
309 }
310
311 fn is_terminator(&self) -> bool {
312 true
313 }
314
315 fn args(&self) -> Args {
316 let mut args = smallvec![self.condition];
317 args.extend_from_slice(&self.success_args);
318 args.extend_from_slice(&self.failure_args);
319 args
320 }
321}
322
323#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
324pub struct Return {
325 pub ptr: LocalValueId,
326 pub value: Option<LocalValueId>,
327}
328
329impl MnemonicKind for Return {
330 fn opcode(&self) -> &'static str {
331 "return"
332 }
333
334 fn is_terminator(&self) -> bool {
335 true
336 }
337
338 fn args(&self) -> Args {
339 let mut args = smallvec![self.ptr];
340 if let Some(value) = self.value {
341 args.push(value);
342 }
343 args
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
348pub struct ReturnValue {
349 pub value: LocalValueId,
350}
351
352impl MnemonicKind for ReturnValue {
353 fn opcode(&self) -> &'static str {
354 "returnvalue"
355 }
356
357 fn is_terminator(&self) -> bool {
358 true
359 }
360
361 fn args(&self) -> Args {
362 smallvec![self.value]
363 }
364}
365
366#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
383pub struct BadInsn;
384
385impl MnemonicKind for BadInsn {
386 fn opcode(&self) -> &'static str {
387 "badinsn"
388 }
389
390 fn is_terminator(&self) -> bool {
391 true
392 }
393
394 fn args(&self) -> Args {
395 smallvec![]
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use crate::value::QCodeMut;
402 use wazabin_qcode_macro::qcode;
403
404 use crate::{
405 context::Context,
406 testing::TestContext,
407 value::{
408 BasicBlock, FunctionBody, Instruction,
409 insn::{Callee, Mnemonic},
410 },
411 };
412
413 #[test]
414 fn minted_callee_is_not_a_call_graph_target_and_renders_explicitly() {
415 let mut ctx = Context::new();
416 let func = FunctionBody::make(&mut ctx, "caller".into()).unwrap().id;
417 let id = crate::value::InstructionRef::from_mnemonic(
418 &mut ctx,
419 func,
420 Mnemonic::Call(super::Call {
421 target: Callee::Minted(7),
422 args: vec![],
423 clobbers: vec![],
424 tag: Default::default(),
425 }),
426 0,
427 )
428 .id;
429
430 let insn = Instruction::from_id(&ctx, id);
431 assert_eq!(insn.mnemonic().call_target(), None);
432 assert_eq!(insn.as_statement().to_string(), "call fn <minted:7>();");
433 assert_eq!(Callee::Minted(7).real(), None);
434 assert_eq!(Callee::Minted(7).minted(), Some(7));
435 assert_eq!(Callee::from(func).real(), Some(func));
436 }
437
438 #[test]
439 #[should_panic(expected = "execution requires a real callee; minted placeholder #3 escaped")]
440 fn execution_boundary_rejects_minted_callee() {
441 Callee::Minted(3).expect_real("execution");
442 }
443
444 #[test]
445 fn tail_call_is_a_function_level_terminator() {
446 use crate::value::{BasicBlock, FunctionBody};
447
448 let mut ctx = Context::new();
449 let callee = FunctionBody::make_at_addr(&mut ctx, 0x2000, None).id;
450 let block = {
451 let f = ctx.anon_function();
452 BasicBlock::make(&mut ctx, f).id
453 };
454 let insn = ctx.builder(block).push_tail_call(callee).id;
455
456 let insn = Instruction::from_id(&ctx, insn);
457 assert!(insn.is_terminator());
458 assert_eq!(insn.mnemonic().call_target(), Some(callee));
461 assert!(insn.mnemonic().target_blocks().is_empty());
462 assert_eq!(insn.as_statement().to_string(), "tailcall fn fn_2000();");
463 }
464
465 #[test]
466 fn qcode_emits_branch() {
467 let mut ctx = Context::new();
468 qcode!(
469 ctx,
470 "
471 <block>
472 goto <done>;
473 <done>
474 goto <0x1001>;
475 "
476 );
477
478 let block = BasicBlock::from_id(&ctx, block);
479 let last = block.iter().last().expect("block has instructions");
480 assert!(matches!(last.mnemonic(), Mnemonic::Branch(_)));
481 assert_eq!(last.as_statement().to_string(), "goto <done>;");
482 }
483
484 #[test]
485 fn qcode_emits_branchind() {
486 let mut ctx = Context::new();
487 qcode!(
488 ctx,
489 "
490 <block>
491 local i64 ptr;
492 goto [ptr];
493 "
494 );
495
496 let block = BasicBlock::from_id(&ctx, block);
497 let last = block.iter().last().expect("block has instructions");
498 assert!(matches!(last.mnemonic(), Mnemonic::BranchInd(_)));
499 }
500
501 #[test]
502 fn qcode_emits_cbranch() {
503 let mut ctx = Context::new();
504 qcode!(
505 ctx,
506 "
507 varnode i8 cond;
508
509 <block>
510 %c = load(cond:1, &cond);
511 if %c goto <then_lbl> else goto <else_lbl>;
512
513 <then_lbl>
514 goto <0x1001>;
515
516 <else_lbl>
517 goto <0x1002>;
518 "
519 );
520
521 let block = BasicBlock::from_id(&ctx, block);
522 let last = block.iter().last().expect("block has instructions");
523 assert!(matches!(last.mnemonic(), Mnemonic::CBranch(_)));
524 }
525
526 #[test]
527 fn qcode_emits_call() {
528 let mut ctx = Context::new();
529 qcode!(
530 ctx,
531 "
532 <block>
533 call <target>;
534 "
535 );
536
537 let block = BasicBlock::from_id(&ctx, block);
538 let last = block.iter().last().expect("block has instructions");
539 assert!(matches!(last.mnemonic(), Mnemonic::Call(_)));
540 }
541
542 #[test]
543 fn call_display_shows_named_args_with_fallbacks() {
544 let mut tc = TestContext::new();
545 let callee = FunctionBody::make(&mut tc.ctx, "callee".into()).unwrap().id;
546 FunctionBody::from_id_mut(&mut tc.ctx, callee).set_extern_interface(
547 crate::value::ExternInterface {
548 args: vec![crate::value::ExternArg {
549 slot: crate::value::ExternSlot::Reg(tc.r0, 8),
550 name: Some("r0".into()),
551 attrs: Default::default(),
552 }],
553 },
554 );
555
556 let block = {
557 let __f = tc.ctx.anon_function();
558 BasicBlock::make(&mut tc.ctx, __f)
559 }
560 .id;
561 let call_id = {
562 let mut builder = tc.ctx.builder(block);
563 builder.push_call(callee).id
564 };
565
566 let first = tc.ctx.get_const(1u64, 8).id();
567 let second = tc.ctx.get_const(2u64, 8).id();
568 tc.ctx.replace_instruction_mnemonic(
569 call_id,
570 Mnemonic::Call(super::Call {
571 target: Callee::Real(callee),
572 args: vec![first.strip_func(), second.strip_func()],
573 clobbers: vec![],
574 tag: Default::default(),
575 }),
576 );
577
578 let rendered = Instruction::from_id(&tc.ctx, call_id)
579 .as_statement()
580 .to_string();
581 assert_eq!(rendered, "call fn callee(@r0=i64 0x1, @arg1=i64 0x2);");
582 }
583
584 #[test]
585 fn call_display_names_stack_passed_arg() {
586 use crate::{space::Space, value::Varnode};
587
588 let mut tc = TestContext::new();
589
590 let stack_space = tc.ctx.add_space(Space::new(Some("stack"), 1, 4));
593 let stack_input = Varnode::make(&mut tc.ctx, 4, 4, stack_space).id;
594
595 let callee = FunctionBody::make(&mut tc.ctx, "callee".into()).unwrap().id;
596 FunctionBody::from_id_mut(&mut tc.ctx, callee).set_extern_interface(
599 crate::value::ExternInterface {
600 args: vec![crate::value::ExternArg {
601 slot: crate::value::ExternSlot::Stack { offset: 4, size: 4 },
602 name: Some("stack_4".into()),
603 attrs: Default::default(),
604 }],
605 },
606 );
607 let _ = stack_input;
608
609 let block = {
610 let __f = tc.ctx.anon_function();
611 BasicBlock::make(&mut tc.ctx, __f)
612 }
613 .id;
614 let call_id = {
615 let mut builder = tc.ctx.builder(block);
616 builder.push_call(callee).id
617 };
618
619 let arg = tc.ctx.get_const(7u64, 4).id();
620 tc.ctx.replace_instruction_mnemonic(
621 call_id,
622 Mnemonic::Call(super::Call {
623 target: Callee::Real(callee),
624 args: vec![arg.strip_func()],
625 clobbers: vec![],
626 tag: Default::default(),
627 }),
628 );
629
630 let rendered = Instruction::from_id(&tc.ctx, call_id)
631 .as_statement()
632 .to_string();
633 assert_eq!(rendered, "call fn callee(@stack_4=i32 0x7);");
635 }
636
637 #[test]
638 fn qcode_emits_callind() {
639 let mut ctx = Context::new();
640 qcode!(
641 ctx,
642 "
643 <block>
644 local i64 ptr;
645 call [ptr];
646 "
647 );
648
649 let block = BasicBlock::from_id(&ctx, block);
650 let last = block.iter().last().expect("block has instructions");
651 assert!(matches!(last.mnemonic(), Mnemonic::CallInd(_)));
652 }
653
654 #[test]
655 fn qcode_emits_return() {
656 let mut ctx = Context::new();
657 qcode!(
658 ctx,
659 "
660 <block>
661 local i64 ptr;
662 return at ptr;
663 "
664 );
665
666 let block = BasicBlock::from_id(&ctx, block);
667 let last = block.iter().last().expect("block has instructions");
668 assert!(matches!(last.mnemonic(), Mnemonic::Return(_)));
669 }
670
671 #[test]
672 fn qcode_emits_lambda_apply_and_value_return() {
673 let mut ctx = Context::new();
674 qcode!(
675 ctx,
676 "
677 lambda rec:
678 <entry @s:i64>
679 %next = @s + 1;
680 %out = apply rec(%next);
681 return %out;
682 "
683 );
684
685 let rec = FunctionBody::from_name(&ctx, "rec").expect("lambda exists");
686 assert!(rec.is_lambda());
687 let entry = rec.root().expect("lambda has root");
688 let insns = entry.instruction_ids();
689 let apply = ctx.get_insn(insns[1]);
690 assert!(!apply.is_terminator(), "apply is a value instruction");
691 assert!(matches!(apply.mnemonic(), Mnemonic::Apply(_)));
692 assert!(matches!(
693 ctx.get_insn(*insns.last().unwrap()).mnemonic(),
694 Mnemonic::ReturnValue(_)
695 ));
696 assert!(apply.as_statement().to_string().contains("apply rec("));
697 assert_eq!(
698 ctx.get_insn(*insns.last().unwrap())
699 .as_statement()
700 .to_string(),
701 "return i64 %out;"
702 );
703 }
704
705 #[test]
706 fn qcode_multi_block_with_label() {
707 let mut ctx = Context::new();
708 qcode!(
709 ctx,
710 "
711 varnode i32 V;
712
713 <block>
714 goto <body>;
715
716 <body>
717 %sum = i64 &V + i64 0x1;
718 goto <0x1001>;
719 "
720 );
721
722 let entry = BasicBlock::from_id(&ctx, block);
724 let entry_last = entry.iter().last().expect("entry has instructions");
725 assert!(matches!(entry_last.mnemonic(), Mnemonic::Branch(_)));
726
727 let Mnemonic::Branch(branch) = entry_last.mnemonic() else {
729 panic!("expected branch");
730 };
731 let body = BasicBlock::from_id(&ctx, crate::value::BlockId::new(block.func, branch.target));
732 assert!(!body.is_empty());
733 }
734
735 #[test]
736 fn qcode_cbranch_target_and_fallthrough_are_distinct_blocks() {
737 let mut ctx = Context::new();
738 qcode!(
739 ctx,
740 "
741 varnode i8 cond;
742
743 <block>
744 %c = load(cond:1, &cond);
745 if %c goto <then_lbl> else goto <else_lbl>;
746
747 <then_lbl>
748 goto <0x1001>;
749
750 <else_lbl>
751 goto <0x1002>;
752 "
753 );
754
755 let block = BasicBlock::from_id(&ctx, block);
756 let last = block.iter().last().expect("block has instructions");
757 let Mnemonic::CBranch(cbranch) = last.mnemonic() else {
758 panic!("expected cbranch");
759 };
760 assert_ne!(
761 cbranch.success_block, cbranch.failure_block,
762 "target and fallthrough must be distinct"
763 );
764 }
765
766 #[test]
767 fn branch_with_args_stores_args() {
768 use crate::value::ValueId;
769
770 let mut ctx = Context::new();
771 qcode!(
772 ctx,
773 "
774 <src @a>
775 goto <dst @x=@a>;
776 <dst @x>
777 goto <0x1001>;
778 "
779 );
780
781 let src_block = BasicBlock::from_id(&ctx, src);
782 let last = src_block.iter().last().expect("block has instructions");
783 let Mnemonic::Branch(branch) = last.mnemonic() else {
784 panic!("expected branch");
785 };
786 assert_eq!(branch.target, dst.local);
787 assert_eq!(branch.args.len(), 1);
788 assert_eq!(branch.args[0], ValueId::BlockParam(a).strip_func());
789 }
790
791 #[test]
792 fn cbranch_with_per_target_args_are_independent() {
793 use crate::value::ValueId;
794
795 let mut ctx = Context::new();
796 qcode!(
797 ctx,
798 "
799 <src @cond:i8 @then_arg:i64 @else_arg:i64>
800 if @cond goto <then_lbl @x=@then_arg> else goto <else_lbl @y=@else_arg>;
801 <then_lbl @x:i64>
802 goto <0x1001>;
803 <else_lbl @y:i64>
804 goto <0x1002>;
805 "
806 );
807
808 let src_block = BasicBlock::from_id(&ctx, src);
809 let insn = src_block.iter().last().expect("src has cbranch");
810 let Mnemonic::CBranch(cbranch) = insn.mnemonic() else {
811 panic!("expected cbranch");
812 };
813 assert_eq!(
814 cbranch.success_args,
815 [ValueId::BlockParam(then_arg).strip_func()]
816 );
817 assert_eq!(
818 cbranch.failure_args,
819 [ValueId::BlockParam(else_arg).strip_func()]
820 );
821 assert_ne!(cbranch.success_block, cbranch.failure_block);
822 }
823
824 #[test]
825 fn branch_args_display() {
826 let mut ctx = Context::new();
827 qcode!(
828 ctx,
829 "
830 <src @a>
831 goto <done @x=@a>;
832 <done @x>
833 goto <0x1001>;
834 "
835 );
836
837 let src_block = BasicBlock::from_id(&ctx, src);
838 let last = src_block.iter().last().expect("block has instructions");
839 assert_eq!(last.as_statement().to_string(), "goto <done @x=i0 @a>;");
840 }
841
842 #[test]
843 fn branch_args_are_ordered_by_target_params() {
844 use crate::value::ValueId;
845
846 let mut ctx = Context::new();
847 qcode!(
848 ctx,
849 "
850 <src @a @b>
851 goto <done @y=@a @x=@b>;
852 <done @x @y>
853 goto <0x1001>;
854 "
855 );
856
857 let src_block = BasicBlock::from_id(&ctx, src);
858 let last = src_block.iter().last().expect("block has instructions");
859 let Mnemonic::Branch(branch) = last.mnemonic() else {
860 panic!("expected branch");
861 };
862 assert_eq!(
863 branch.args,
864 [
865 ValueId::BlockParam(b).strip_func(),
866 ValueId::BlockParam(a).strip_func()
867 ]
868 );
869 assert_eq!(
870 last.as_statement().to_string(),
871 "goto <done @x=i0 @b @y=i0 @a>;"
872 );
873 }
874}