Skip to main content

vihaco_cpu/
component.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use eyre::Result;
5use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
6
7use crate::StepOutcome;
8use crate::data::CPU;
9use crate::instruction::RuntimeInstruction;
10use vihaco::Effects;
11use vihaco::program::{Type, Value};
12use vihaco::{Execute, Execution, StepResult, frame::Frame, traits::*};
13
14impl Reset for CPU {
15    fn reset(&mut self) {
16        self.frames.clear();
17        self.heap.clear();
18        self.stack.clear();
19        self.span = (0, 0, 0);
20        self.pending_pc = None;
21        self.current_pc = 0;
22        self.return_values.clear();
23    }
24}
25
26impl CPU {
27    pub fn execute_instruction(&mut self, inst: RuntimeInstruction) -> eyre::Result<StepOutcome> {
28        self.clear_pending_pc();
29        use RuntimeInstruction::*;
30        match inst {
31            Span(file, start, end) => self.op_span(file, start, end),
32            Label | FunctionStart | FunctionEnd => Ok(StepOutcome::Continue),
33            Breakpoint => Ok(StepOutcome::Breakpoint),
34            Branch(target) => self.op_branch(target),
35            ConditionalBranch(true_target, false_target) => {
36                self.op_conditional_branch(true_target, false_target)
37            }
38            Return(keep) => self.op_return(keep),
39            Call(arity, target) => self.op_call(arity, target),
40            IndirectCall => self.op_indirect_call(),
41            Halt => Ok(StepOutcome::Halt),
42            Print => Err(eyre::eyre!(
43                "Print must be handled via execute with CPUMessage::Print"
44            )),
45            Load(ty, addr) => self.op_load(ty, addr),
46            Store(ty, addr) => self.op_store(ty, addr),
47            Dup => self.op_dup(),
48            HeapAlloc(n_elements) => self.op_heap_alloc(n_elements),
49            GetItem => self.op_get_item(),
50            HeapDealloc => self.op_heap_dealloc(),
51            Const(v) => self.op_const(v),
52            Add(ty) => self.op_add(ty),
53            Sub(ty) => self.op_sub(ty),
54            Mul(ty) => self.op_mul(ty),
55            Div(ty) => self.op_div(ty),
56            Rem(ty) => self.op_rem(ty),
57            Neg(ty) => self.op_neg(ty),
58            Shl(ty) => self.op_shl(ty),
59            Shr(ty) => self.op_shr(ty),
60            Rol(ty) => self.op_rol(ty),
61            Ror(ty) => self.op_ror(ty),
62            BitAnd(ty) => self.op_bitand(ty),
63            BitOr(ty) => self.op_bitor(ty),
64            BitXor(ty) => self.op_bitxor(ty),
65            Not => self.op_not(),
66            And => self.op_and(),
67            Or => self.op_or(),
68            Xor => self.op_xor(),
69            Eq(ty) => self.op_eq(ty),
70            Ne(ty) => self.op_ne(ty),
71            Lt(ty) => self.op_lt(ty),
72            Gt(ty) => self.op_gt(ty),
73            Le(ty) => self.op_le(ty),
74            Ge(ty) => self.op_ge(ty),
75        }
76    }
77}
78
79#[derive(Debug, Clone, PartialEq)]
80pub enum CPUMessage {
81    None,
82    FunctionInfo { arity: u32, start_address: u32 },
83    Print(String),
84}
85
86impl vihaco::Message for CPUMessage {}
87
88impl CPU {
89    fn execute(
90        &mut self,
91        inst: RuntimeInstruction,
92        msg: CPUMessage,
93    ) -> eyre::Result<Effects<StepOutcome>> {
94        use RuntimeInstruction::*;
95        match (inst, msg) {
96            (Print, CPUMessage::Print(text)) => {
97                self.stack_pop()?;
98                drop(text);
99                Ok(Effects::one(StepOutcome::Continue))
100            }
101            (Print, _) => Err(eyre::eyre!("Print requires CPUMessage::Print")),
102            (_, CPUMessage::Print(_)) => Err(eyre::eyre!(
103                "CPUMessage::Print is only valid for Print instruction"
104            )),
105            (
106                inst,
107                CPUMessage::FunctionInfo {
108                    arity,
109                    start_address,
110                },
111            ) => {
112                self.stack_push(arity);
113                self.stack_push(start_address);
114                self.execute_instruction(inst).map(Effects::one)
115            }
116            (inst, CPUMessage::None) => self.execute_instruction(inst).map(Effects::one),
117        }
118    }
119}
120
121impl Execute<RuntimeInstruction> for CPU {
122    type Message = CPUMessage;
123    type Effect = StepOutcome;
124    type Fault = eyre::Report;
125
126    fn execute(
127        &mut self,
128        inst: &RuntimeInstruction,
129        msg: Self::Message,
130    ) -> eyre::Result<StepResult<Self::Effect>> {
131        Ok(StepResult {
132            effects: self.execute(inst.clone(), msg)?,
133            execution: Execution::Complete,
134        })
135    }
136}
137
138impl CPU {
139    pub fn op_span(&mut self, file: u32, start: u32, end: u32) -> eyre::Result<StepOutcome> {
140        self.span = (file, start, end);
141        Ok(StepOutcome::Continue)
142    }
143
144    pub fn op_branch(&mut self, target: u32) -> eyre::Result<StepOutcome> {
145        self.set_pending_pc(target);
146        Ok(StepOutcome::Continue)
147    }
148
149    pub fn op_conditional_branch(
150        &mut self,
151        true_target: u32,
152        false_target: u32,
153    ) -> eyre::Result<StepOutcome> {
154        let cond = self.stack.pop().ok_or(eyre::eyre!("stack underflow"))?;
155        match cond {
156            Value::Bool(true) => {
157                self.set_pending_pc(true_target);
158                Ok(StepOutcome::Continue)
159            }
160            Value::Bool(false) => {
161                self.set_pending_pc(false_target);
162                Ok(StepOutcome::Continue)
163            }
164            _ => Err(eyre::eyre!("type error: expected bool on stack")),
165        }
166    }
167
168    pub fn op_return(&mut self, keep: u32) -> eyre::Result<StepOutcome> {
169        let frame = self.pop_frame()?;
170        if self.stack.len() - frame.base < (keep as usize) {
171            return Err(eyre::eyre!("not enough values to return"));
172        }
173
174        // Collect return values before truncating
175        let top = self.stack.len() - keep as usize;
176        let return_values: Vec<Value> = self.stack[top..].to_vec();
177        self.stack.drain(frame.base..top);
178
179        if self.get_frame().is_err() {
180            // No more frames - program is returning
181            self.set_return_values(return_values);
182            Ok(StepOutcome::Return)
183        } else {
184            self.set_pending_pc(frame.ret_pc);
185            Ok(StepOutcome::Continue)
186        }
187    }
188
189    pub fn op_call(&mut self, arity: u32, target: u32) -> eyre::Result<StepOutcome> {
190        if self.stack.len() < (arity as usize) {
191            return Err(eyre::eyre!(
192                "not enough arguments on stack to call function"
193            ));
194        }
195
196        let base = self.stack.len() - (arity as usize);
197        let frame = Frame {
198            base,
199            span: self.span,
200            function: None,
201            ret_pc: self.current_pc + 1,
202        };
203        self.push_frame(frame);
204        self.set_pending_pc(target);
205        Ok(StepOutcome::Continue)
206    }
207
208    pub fn op_indirect_call(&mut self) -> eyre::Result<StepOutcome> {
209        // simliar order to op_call but from the stack
210        let target: u32 = self.stack_pop()?.try_into()?;
211        let arity: u32 = self.stack_pop()?.try_into()?;
212        let f = self.stack_pop()?.get_function_ref()?;
213
214        if self.stack.len() < (arity as usize) {
215            return Err(eyre::eyre!(
216                "not enough arguments on stack to call function"
217            ));
218        }
219
220        let base = self.stack.len() - (arity as usize);
221        let frame = Frame {
222            base,
223            span: self.span,
224            function: Some(f as usize),
225            ret_pc: self.current_pc + 1,
226        };
227        self.push_frame(frame);
228        self.set_pending_pc(target);
229        Ok(StepOutcome::Continue)
230    }
231
232    fn op_load(&mut self, ty: Type, addr: u32) -> eyre::Result<StepOutcome> {
233        // addr should be local to frame.
234        let value = self.get_local(addr as usize)?;
235        if value.type_of() != ty {
236            return Err(eyre::eyre!(format!(
237                "type error: expected {:?} at address {}, got {:?}",
238                ty,
239                addr,
240                value.type_of()
241            )));
242        }
243        self.stack_push(*value);
244        Ok(StepOutcome::Continue)
245    }
246
247    pub fn op_store(&mut self, ty: Type, addr: u32) -> Result<StepOutcome> {
248        let v: Value = self.stack_pop()?;
249        log::debug!("store value {:?} at addr {}", v, addr);
250        if !v.is_undefined() && v.type_of() != ty {
251            return Err(eyre::eyre!("Type mismatch"));
252        }
253        *self.get_local_mut(addr as usize)? = v;
254        Ok(StepOutcome::Continue)
255    }
256
257    pub fn op_dup(&mut self) -> Result<StepOutcome> {
258        let v = *self.stack_top()?;
259        self.stack.push(v);
260        Ok(StepOutcome::Continue)
261    }
262
263    pub fn op_heap_alloc(&mut self, n_elements: u32) -> Result<StepOutcome> {
264        let n: usize = n_elements as usize;
265        if self.stack.len() < n {
266            return Err(eyre::eyre!("stack underflow"));
267        }
268        let start = self.stack.len() - n;
269        let values: Box<[Value]> = self.stack.drain(start..).collect();
270        let heap_id = self.push_heap_object(values);
271        self.stack_push(Value::HeapRef(heap_id));
272        Ok(StepOutcome::Continue)
273    }
274
275    pub fn op_get_item(&mut self) -> Result<StepOutcome> {
276        let index = Self::heap_index(self.stack_pop()?)?;
277        let heap_id = self.stack_pop()?.get_heap_ref()?;
278        let value = *self
279            .heap_object(heap_id)?
280            .get(index)
281            .ok_or_else(|| eyre::eyre!("heap index {} out of bounds", index))?;
282        self.stack_push(value);
283        Ok(StepOutcome::Continue)
284    }
285
286    pub fn op_heap_dealloc(&mut self) -> Result<StepOutcome> {
287        let id = self.stack_pop()?.get_heap_ref()?;
288        self.dealloc_heap_object(id)?;
289        Ok(StepOutcome::Continue)
290    }
291
292    pub fn op_const(&mut self, v: Value) -> Result<StepOutcome> {
293        self.stack.push(v);
294        Ok(StepOutcome::Continue)
295    }
296
297    fn heap_index(value: Value) -> Result<usize> {
298        match value {
299            Value::U32(index) => Ok(index as usize),
300            Value::U64(index) => usize::try_from(index)
301                .map_err(|_| eyre::eyre!("heap index {} does not fit in usize", index)),
302            Value::I64(index) if index >= 0 => usize::try_from(index)
303                .map_err(|_| eyre::eyre!("heap index {} does not fit in usize", index)),
304            Value::I64(index) => Err(eyre::eyre!(
305                "heap index must be non-negative, got {}",
306                index
307            )),
308            _ => Err(eyre::eyre!(
309                "type error: expected integer heap index, got {:?}",
310                value.type_of()
311            )),
312        }
313    }
314}
315
316#[cfg(test)]
317#[allow(clippy::items_after_test_module)]
318mod tests {
319    use super::*;
320    use vihaco::{Effects, Execute, frame::Frame, instruction::OpCode, traits::StackMemory};
321
322    fn execute(
323        cpu: &mut CPU,
324        instruction: RuntimeInstruction,
325        message: CPUMessage,
326    ) -> eyre::Result<Effects<StepOutcome>> {
327        Execute::execute(cpu, &instruction, message).map(|result| result.effects)
328    }
329
330    #[test]
331    fn cpu_generated_component_executes_instruction_without_message() {
332        let mut cpu = CPU::default();
333
334        execute(
335            &mut cpu,
336            RuntimeInstruction::Const(Value::I64(7)),
337            CPUMessage::None,
338        )
339        .unwrap();
340
341        assert_eq!(cpu.stack(), &vec![Value::I64(7)]);
342    }
343
344    #[test]
345    fn execute_instruction_applies_control_flow_without_action() {
346        let mut cpu = CPU::default();
347
348        let branch = cpu
349            .execute_instruction(RuntimeInstruction::Branch(9))
350            .unwrap();
351        assert_eq!(branch, StepOutcome::Continue);
352        assert_eq!(cpu.take_pending_pc(), Some(9));
353
354        let halt = cpu.execute_instruction(RuntimeInstruction::Halt).unwrap();
355        assert_eq!(halt, StepOutcome::Halt);
356        assert_eq!(cpu.take_pending_pc(), None);
357    }
358
359    #[test]
360    fn op_return_stores_terminal_values_in_runtime_state() {
361        let mut cpu = CPU::default();
362        cpu.push_frame(Frame {
363            base: 0,
364            span: (0, 0, 0),
365            function: None,
366            ret_pc: 0,
367        });
368        cpu.stack_push(Value::I64(7));
369
370        let outcome = cpu
371            .execute_instruction(RuntimeInstruction::Return(1))
372            .unwrap();
373
374        assert_eq!(outcome, StepOutcome::Return);
375        assert_eq!(cpu.return_values(), &[Value::I64(7)]);
376    }
377
378    #[test]
379    fn op_return_restores_callers_pc() {
380        let mut cpu = CPU {
381            current_pc: 10,
382            ..Default::default()
383        };
384        // Outer ("main") frame so the inner Return takes the Continue branch.
385        cpu.push_frame(Frame {
386            base: 0,
387            span: (0, 0, 0),
388            function: None,
389            ret_pc: 0,
390        });
391
392        // Caller would be executing `call 0, 100` at some PC; op_call sets
393        // pending_pc to the callee target.
394        cpu.execute_instruction(RuntimeInstruction::Call(0, 100))
395            .unwrap();
396        assert_eq!(cpu.take_pending_pc(), Some(100));
397        assert_eq!(cpu.frames[1].ret_pc, 11);
398
399        // Callee returns immediately. pending_pc should be restored to the
400        // instruction after the call.
401        let outcome = cpu
402            .execute_instruction(RuntimeInstruction::Return(0))
403            .unwrap();
404        assert_eq!(outcome, StepOutcome::Continue);
405        assert_eq!(cpu.take_pending_pc(), Some(11),);
406    }
407
408    #[test]
409    fn op_indirect_call_records_return_pc_after_call_site() {
410        let mut cpu = CPU {
411            current_pc: 10,
412            ..Default::default()
413        };
414        cpu.push_frame(Frame {
415            base: 0,
416            span: (0, 0, 0),
417            function: None,
418            ret_pc: 0,
419        });
420
421        // IndirectCall pops (top → bottom): target, arity, FunctionRef.
422        cpu.stack_push(Value::FunctionRef(7));
423        cpu.stack_push(Value::U32(0));
424        cpu.stack_push(Value::U32(100));
425
426        cpu.execute_instruction(RuntimeInstruction::IndirectCall)
427            .unwrap();
428        assert_eq!(cpu.take_pending_pc(), Some(100));
429        assert_eq!(cpu.frames[1].ret_pc, 11);
430
431        let outcome = cpu
432            .execute_instruction(RuntimeInstruction::Return(0))
433            .unwrap();
434        assert_eq!(outcome, StepOutcome::Continue);
435        assert_eq!(cpu.take_pending_pc(), Some(11));
436    }
437
438    #[test]
439    fn op_return_keeps_bottom_of_frame_when_callee_leaves_scratch() {
440        let mut cpu = CPU::default();
441        // Outer frame so Return takes the Continue branch.
442        cpu.push_frame(Frame {
443            base: 0,
444            span: (0, 0, 0),
445            function: None,
446            ret_pc: 0,
447        });
448
449        // Simulate a callee frame holding [scratch_a, scratch_b, return_val]
450        // where only `return_val` (the top) should survive `ret 1`.
451        cpu.push_frame(Frame {
452            base: 0,
453            span: (0, 0, 0),
454            function: None,
455            ret_pc: 0,
456        });
457        cpu.stack_push(Value::I64(111)); // scratch — bottom of callee frame
458        cpu.stack_push(Value::I64(222)); // scratch — middle
459        cpu.stack_push(Value::I64(999)); // intended return value — top
460
461        let outcome = cpu
462            .execute_instruction(RuntimeInstruction::Return(1))
463            .unwrap();
464        assert_eq!(outcome, StepOutcome::Continue);
465
466        assert_eq!(cpu.stack(), &vec![Value::I64(999)],);
467    }
468
469    #[test]
470    fn op_heap_alloc_preserves_natural_push_order_and_returns_heap_ref() {
471        let mut cpu = CPU::default();
472        cpu.stack_push(Value::I64(10));
473        cpu.stack_push(Value::I64(20));
474        cpu.stack_push(Value::I64(30));
475
476        let outcome = cpu
477            .execute_instruction(RuntimeInstruction::HeapAlloc(3))
478            .unwrap();
479
480        assert_eq!(outcome, StepOutcome::Continue);
481        assert_eq!(cpu.stack(), &vec![Value::HeapRef(0)]);
482        assert_eq!(
483            cpu.heap.get(0).unwrap(),
484            &[Value::I64(10), Value::I64(20), Value::I64(30)]
485        );
486    }
487
488    #[test]
489    fn op_heap_alloc_supports_empty_heap_objects() {
490        let mut cpu = CPU::default();
491
492        let outcome = cpu
493            .execute_instruction(RuntimeInstruction::HeapAlloc(0))
494            .unwrap();
495
496        assert_eq!(outcome, StepOutcome::Continue);
497        assert_eq!(cpu.stack(), &vec![Value::HeapRef(0)]);
498        assert_eq!(cpu.heap.get(0).unwrap(), &[] as &[Value]);
499    }
500
501    #[test]
502    fn op_get_item_reads_heap_value() {
503        let mut cpu = CPU::default();
504        cpu.stack_push(Value::I64(10));
505        cpu.stack_push(Value::I64(20));
506        cpu.stack_push(Value::I64(30));
507        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(3))
508            .unwrap();
509        cpu.stack_push(Value::U32(1));
510
511        let outcome = cpu
512            .execute_instruction(RuntimeInstruction::GetItem)
513            .unwrap();
514
515        assert_eq!(outcome, StepOutcome::Continue);
516        assert_eq!(cpu.stack(), &vec![Value::I64(20)]);
517    }
518
519    #[test]
520    fn op_get_item_rejects_non_heap_refs() {
521        let mut cpu = CPU::default();
522        cpu.stack_push(Value::I64(7));
523        cpu.stack_push(Value::U32(0));
524
525        let err = cpu
526            .execute_instruction(RuntimeInstruction::GetItem)
527            .unwrap_err();
528
529        assert!(err.to_string().contains("HeapRef"));
530    }
531
532    #[test]
533    fn op_get_item_rejects_invalid_heap_ids() {
534        let mut cpu = CPU::default();
535        cpu.stack_push(Value::HeapRef(99));
536        cpu.stack_push(Value::U32(0));
537
538        let err = cpu
539            .execute_instruction(RuntimeInstruction::GetItem)
540            .unwrap_err();
541
542        assert!(err.to_string().contains("heap"));
543    }
544
545    #[test]
546    fn op_get_item_rejects_out_of_bounds_indices() {
547        let mut cpu = CPU::default();
548        cpu.stack_push(Value::I64(10));
549        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
550            .unwrap();
551        cpu.stack_push(Value::U32(3));
552
553        let err = cpu
554            .execute_instruction(RuntimeInstruction::GetItem)
555            .unwrap_err();
556
557        assert!(err.to_string().contains("index"));
558    }
559
560    #[test]
561    fn reset_clears_heap_allocations() {
562        let mut cpu = CPU::default();
563        cpu.stack_push(Value::I64(10));
564        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
565            .unwrap();
566
567        cpu.reset();
568
569        assert!(cpu.heap.is_empty());
570        assert!(cpu.stack().is_empty());
571    }
572
573    #[test]
574    fn cpu_instruction_opcodes_follow_variant_order_without_explicit_attributes() {
575        assert_eq!(RuntimeInstruction::Span(0, 0, 0).opcode(), 0);
576        assert_eq!(RuntimeInstruction::Label.opcode(), 1);
577        assert_eq!(RuntimeInstruction::FunctionStart.opcode(), 2);
578        assert_eq!(RuntimeInstruction::HeapAlloc(1).opcode(), 15);
579        assert_eq!(RuntimeInstruction::Const(Value::I64(1)).opcode(), 18);
580        assert_eq!(RuntimeInstruction::Ge(Type::I64).opcode(), 41);
581    }
582
583    #[test]
584    fn execute_generated_dispatches_instruction_without_message() {
585        let mut cpu = CPU::default();
586        cpu.push_frame(Frame {
587            base: 0,
588            span: (0, 0, 0),
589            function: None,
590            ret_pc: 0,
591        });
592
593        let outcome = execute(
594            &mut cpu,
595            RuntimeInstruction::Const(Value::I64(99)),
596            CPUMessage::None,
597        )
598        .unwrap();
599
600        assert_eq!(outcome, Effects::one(StepOutcome::Continue));
601        assert_eq!(cpu.stack(), &vec![Value::I64(99)]);
602    }
603
604    #[test]
605    fn execute_generated_function_info_pushes_arity_and_start_address() {
606        let mut cpu = CPU::default();
607        cpu.push_frame(Frame {
608            base: 0,
609            span: (0, 0, 0),
610            function: None,
611            ret_pc: 0,
612        });
613
614        let outcome = execute(
615            &mut cpu,
616            RuntimeInstruction::Label,
617            CPUMessage::FunctionInfo {
618                arity: 2,
619                start_address: 42,
620            },
621        )
622        .unwrap();
623
624        assert_eq!(outcome, Effects::one(StepOutcome::Continue));
625        // arity pushed first, then start_address
626        assert_eq!(cpu.stack(), &vec![Value::U32(2), Value::U32(42)]);
627    }
628
629    #[test]
630    fn execute_generated_print_returns_control_effect_and_pops_stack() {
631        let mut cpu = CPU::default();
632        cpu.push_frame(Frame {
633            base: 0,
634            span: (0, 0, 0),
635            function: None,
636            ret_pc: 0,
637        });
638        cpu.stack_push(Value::I64(42));
639
640        let outcome = execute(
641            &mut cpu,
642            RuntimeInstruction::Print,
643            CPUMessage::Print("hello".into()),
644        )
645        .unwrap();
646
647        assert_eq!(outcome, Effects::one(StepOutcome::Continue));
648        assert!(cpu.stack().is_empty());
649    }
650
651    #[test]
652    fn execute_generated_print_rejects_wrong_message() {
653        let mut cpu = CPU::default();
654        cpu.push_frame(Frame {
655            base: 0,
656            span: (0, 0, 0),
657            function: None,
658            ret_pc: 0,
659        });
660        cpu.stack_push(Value::I64(42));
661
662        let err = execute(&mut cpu, RuntimeInstruction::Print, CPUMessage::None).unwrap_err();
663
664        assert!(err.to_string().contains("Print requires"));
665    }
666
667    #[test]
668    fn op_heap_dealloc_marks_slot_dead() {
669        let mut cpu = CPU::default();
670        cpu.stack_push(Value::I64(42));
671        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
672            .unwrap();
673        cpu.stack_push(Value::HeapRef(0));
674
675        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
676            .unwrap();
677
678        assert!(
679            cpu.heap
680                .get(0)
681                .unwrap_err()
682                .to_string()
683                .contains("deallocated")
684        );
685    }
686
687    #[test]
688    fn op_heap_dealloc_slot_is_reused_on_next_alloc() {
689        let mut cpu = CPU::default();
690        cpu.stack_push(Value::I64(1));
691        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
692            .unwrap();
693        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
694            .unwrap();
695
696        cpu.stack_push(Value::I64(2));
697        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
698            .unwrap();
699
700        assert_eq!(cpu.stack(), &vec![Value::HeapRef(0)]);
701        assert_eq!(cpu.heap.get(0).unwrap(), &[Value::I64(2)]);
702    }
703
704    #[test]
705    fn op_heap_dealloc_rejects_double_free() {
706        let mut cpu = CPU::default();
707        cpu.stack_push(Value::I64(1));
708        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
709            .unwrap();
710        cpu.stack_push(Value::HeapRef(0));
711        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
712            .unwrap();
713
714        cpu.stack_push(Value::HeapRef(0));
715        let err = cpu
716            .execute_instruction(RuntimeInstruction::HeapDealloc)
717            .unwrap_err();
718
719        assert!(err.to_string().contains("double-free"));
720    }
721
722    #[test]
723    fn op_heap_dealloc_rejects_invalid_id() {
724        let mut cpu = CPU::default();
725        cpu.stack_push(Value::HeapRef(99));
726
727        let err = cpu
728            .execute_instruction(RuntimeInstruction::HeapDealloc)
729            .unwrap_err();
730
731        assert!(err.to_string().contains("invalid heap object id"));
732    }
733
734    #[test]
735    fn reset_clears_free_list() {
736        let mut cpu = CPU::default();
737        cpu.stack_push(Value::I64(1));
738        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
739            .unwrap();
740        cpu.stack_push(Value::HeapRef(0));
741        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
742            .unwrap();
743
744        cpu.reset();
745
746        assert!(cpu.heap.is_empty());
747    }
748}
749
750macro_rules! impl_op_num_binary {
751    ($name:ident, $op:ident) => {
752        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
753            let lhs: Value = self.stack_pop()?;
754            let rhs: Value = self.stack_pop()?;
755            if lhs.type_of() != ty {
756                return Err(eyre::eyre!(
757                    "Type mismatch, expected {} got {} for lhs",
758                    ty,
759                    lhs.type_of()
760                ));
761            }
762
763            if rhs.type_of() != ty {
764                return Err(eyre::eyre!(
765                    "Type mismatch, expected {} got {} for rhs",
766                    ty,
767                    rhs.type_of()
768                ));
769            }
770
771            let output = match (lhs, rhs) {
772                (Value::I64(l), Value::I64(r)) => Value::I64(l.$op(r)),
773                (Value::U32(l), Value::U32(r)) => Value::U32(l.$op(r)),
774                (Value::U64(l), Value::U64(r)) => Value::U64(l.$op(r)),
775                (Value::F64(l), Value::F64(r)) => Value::F64(l.$op(r)),
776                _ => {
777                    return Err(eyre::eyre!(
778                        "cannot {} {} and {}",
779                        stringify!($op),
780                        lhs.type_of(),
781                        rhs.type_of()
782                    ))
783                }
784            };
785            self.stack.push(output);
786            Ok(StepOutcome::Continue)
787        }
788    };
789}
790
791impl CPU {
792    impl_op_num_binary!(op_add, add);
793    impl_op_num_binary!(op_sub, sub);
794    impl_op_num_binary!(op_mul, mul);
795    impl_op_num_binary!(op_div, div);
796    impl_op_num_binary!(op_rem, rem);
797
798    pub fn op_neg(&mut self, ty: Type) -> Result<StepOutcome> {
799        let v: Value = self.stack_pop()?;
800        if v.type_of() != ty {
801            return Err(eyre::eyre!(format!(
802                "Type mismatch, expected {:?} got {:?}",
803                ty,
804                v.type_of()
805            )));
806        }
807
808        let output = match v {
809            Value::I64(i) => Value::I64(-i),
810            Value::F64(f) => Value::F64(-f),
811            _ => return Err(eyre::eyre!(format!("cannot negate {}", v.type_of()))),
812        };
813        self.stack.push(output);
814        Ok(StepOutcome::Continue)
815    }
816}
817
818macro_rules! impl_op_shift {
819    ($name:ident, $op:ident) => {
820        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
821            let rhs: Value = self.stack_pop()?;
822            let lhs: Value = self.stack_pop()?;
823            if lhs.type_of() != ty {
824                return Err(eyre::eyre!(
825                    "Type mismatch, expected {} got {} for lhs",
826                    ty,
827                    lhs.type_of()
828                ));
829            }
830
831            if rhs.type_of() != ty {
832                return Err(eyre::eyre!(
833                    "Type mismatch, expected {} got {} for rhs",
834                    ty,
835                    rhs.type_of()
836                ));
837            }
838            let output = match (lhs, rhs) {
839                (Value::I64(l), Value::I64(r)) => Value::I64(l.$op(r)),
840                (Value::U32(l), Value::U32(r)) => Value::U32(l.$op(r)),
841                (Value::U64(l), Value::U64(r)) => Value::U64(l.$op(r)),
842                _ => {
843                    return Err(eyre::eyre!(format!(
844                        "cannot {} {} and {}",
845                        stringify!($op),
846                        lhs.type_of(),
847                        rhs.type_of()
848                    )))
849                }
850            };
851            self.stack.push(output);
852            Ok(StepOutcome::Continue)
853        }
854    };
855}
856
857impl CPU {
858    impl_op_shift!(op_shl, shl);
859    impl_op_shift!(op_shr, shr);
860}
861
862macro_rules! impl_op_rotate {
863    ($name:ident, $op:ident) => {
864        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
865            let rhs: Value = self.stack_pop()?;
866            let lhs: Value = self.stack_pop()?;
867            if lhs.type_of() != ty {
868                return Err(eyre::eyre!(
869                    "Type mismatch, expected {} got {} for lhs",
870                    ty,
871                    lhs.type_of()
872                ));
873            }
874
875            if rhs.type_of() != Type::U32 {
876                return Err(eyre::eyre!(
877                    "Type mismatch, expected {} got {} for rhs",
878                    Type::U32,
879                    rhs.type_of()
880                ));
881            }
882            let output = match (lhs, rhs) {
883                (Value::I64(l), Value::U32(r)) => Value::I64(l.$op(r)),
884                (Value::U32(l), Value::U32(r)) => Value::U32(l.$op(r)),
885                (Value::U64(l), Value::U32(r)) => Value::U64(l.$op(r)),
886                _ => {
887                    return Err(eyre::eyre!(format!(
888                        "cannot {} {} and {}",
889                        stringify!($op),
890                        lhs.type_of(),
891                        rhs.type_of()
892                    )));
893                }
894            };
895            self.stack.push(output);
896            Ok(StepOutcome::Continue)
897        }
898    };
899}
900
901impl CPU {
902    impl_op_rotate!(op_rol, rotate_left);
903    impl_op_rotate!(op_ror, rotate_right);
904}
905
906macro_rules! impl_op_bitwise {
907    ($name:ident, $op:ident) => {
908        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
909            let rhs: Value = self.stack_pop()?;
910            let lhs: Value = self.stack_pop()?;
911            if lhs.type_of() != ty {
912                return Err(eyre::eyre!(
913                    "Type mismatch, expected {} got {} for lhs",
914                    ty,
915                    lhs.type_of()
916                ));
917            }
918
919            if rhs.type_of() != ty {
920                return Err(eyre::eyre!(
921                    "Type mismatch, expected {} got {} for rhs",
922                    ty,
923                    rhs.type_of()
924                ));
925            }
926            let output = match (lhs, rhs) {
927                (Value::I64(l), Value::I64(r)) => Value::I64(l.$op(r)),
928                (Value::U32(l), Value::U32(r)) => Value::U32(l.$op(r)),
929                (Value::U64(l), Value::U64(r)) => Value::U64(l.$op(r)),
930                _ => {
931                    return Err(eyre::eyre!(format!(
932                        "cannot {} {} and {}",
933                        stringify!($op),
934                        lhs.type_of(),
935                        rhs.type_of()
936                    )))
937                }
938            };
939            self.stack.push(output);
940            Ok(StepOutcome::Continue)
941        }
942    };
943}
944
945impl CPU {
946    impl_op_bitwise!(op_bitand, bitand);
947    impl_op_bitwise!(op_bitor, bitor);
948    impl_op_bitwise!(op_bitxor, bitxor);
949}
950
951macro_rules! impl_boolean_binary {
952    ($name:ident, $op:ident) => {
953        pub fn $name(&mut self) -> Result<StepOutcome> {
954            let rhs: bool = self.stack_pop()?.try_into()?;
955            let lhs: bool = self.stack_pop()?.try_into()?;
956            let output = lhs.$op(rhs);
957            self.stack_push(output);
958            Ok(StepOutcome::Continue)
959        }
960    };
961}
962
963impl CPU {
964    pub fn op_not(&mut self) -> Result<StepOutcome> {
965        let v: bool = self.stack_pop()?.try_into()?;
966        self.stack_push(!v);
967        Ok(StepOutcome::Continue)
968    }
969
970    impl_boolean_binary!(op_and, bitand);
971    impl_boolean_binary!(op_or, bitor);
972    impl_boolean_binary!(op_xor, bitxor);
973}
974
975macro_rules! impl_eq {
976    ($name:ident, $op:ident) => {
977        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
978            let rhs: Value = self.stack_pop()?;
979            let lhs: Value = self.stack_pop()?;
980            if lhs.type_of() != ty {
981                return Err(eyre::eyre!(
982                    "Type mismatch, expected {} got {} for lhs",
983                    ty,
984                    lhs.type_of()
985                ));
986            }
987
988            if rhs.type_of() != ty {
989                return Err(eyre::eyre!(
990                    "Type mismatch, expected {} got {} for rhs",
991                    ty,
992                    rhs.type_of()
993                ));
994            }
995            let output = lhs.$op(&rhs);
996            self.stack_push(output);
997            Ok(StepOutcome::Continue)
998        }
999    };
1000}
1001
1002impl CPU {
1003    impl_eq!(op_eq, eq);
1004    impl_eq!(op_ne, ne);
1005}
1006
1007macro_rules! impl_ordering {
1008    ($name:ident, $op:ident) => {
1009        pub fn $name(&mut self, ty: Type) -> Result<StepOutcome> {
1010            let rhs: Value = self.stack_pop()?;
1011            let lhs: Value = self.stack_pop()?;
1012            if lhs.type_of() != ty {
1013                return Err(eyre::eyre!(
1014                    "Type mismatch, expected {} got {} for lhs",
1015                    ty,
1016                    lhs.type_of()
1017                ));
1018            }
1019
1020            if rhs.type_of() != ty {
1021                return Err(eyre::eyre!(
1022                    "Type mismatch, expected {} got {} for rhs",
1023                    ty,
1024                    rhs.type_of()
1025                ));
1026            }
1027
1028            let output = match (lhs, rhs) {
1029                (Value::Bool(l), Value::Bool(r)) => l.$op(&r),
1030                (Value::I64(l), Value::I64(r)) => l.$op(&r),
1031                (Value::U32(l), Value::U32(r)) => l.$op(&r),
1032                (Value::U64(l), Value::U64(r)) => l.$op(&r),
1033                (Value::F64(l), Value::F64(r)) => l.$op(&r),
1034                _ => {
1035                    return Err(eyre::eyre!(format!(
1036                        "cannot compare {} and {}",
1037                        lhs.type_of(),
1038                        rhs.type_of()
1039                    )))
1040                }
1041            };
1042            self.stack_push(output);
1043            Ok(StepOutcome::Continue)
1044        }
1045    };
1046}
1047
1048impl CPU {
1049    impl_ordering!(op_lt, lt);
1050    impl_ordering!(op_le, le);
1051    impl_ordering!(op_gt, gt);
1052    impl_ordering!(op_ge, ge);
1053}