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