Skip to main content

vihaco_cpu/
component.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use crate::RuntimeInstruction;
5use crate::StepOutcome;
6use crate::Word;
7use crate::data::CPU;
8use crate::word::*;
9use eyre::Result;
10use vihaco::Effects;
11use vihaco::{dispatch, frame::Frame, traits::*};
12
13impl Reset for CPU {
14    fn reset(&mut self) {
15        self.frames.clear();
16        self.heap.clear();
17        self.stack.clear();
18        self.span = (0, 0, 0);
19        self.pending_pc = None;
20        self.current_pc = 0;
21        self.return_values.clear();
22    }
23}
24
25impl CPU {
26    #[inline(always)]
27    fn execute_generated(
28        &mut self,
29        inst: &RuntimeInstruction,
30        msg: CPUMessage,
31    ) -> eyre::Result<Effects<StepOutcome>> {
32        use RuntimeInstruction::*;
33
34        self.clear_pending_pc();
35        match (inst, msg) {
36            (Print, CPUMessage::Print(text)) => {
37                self.stack_pop()?;
38                drop(text);
39                return Ok(Effects::one(StepOutcome::Continue));
40            }
41            (Print, _) => return Err(eyre::eyre!("Print requires CPUMessage::Print")),
42            (_, CPUMessage::Print(_)) => {
43                return Err(eyre::eyre!(
44                    "CPUMessage::Print is only valid for Print instruction"
45                ));
46            }
47            (Call(arity, target), CPUMessage::FunctionInfo { local_count, .. }) => {
48                return self.op_call(*arity, *target, local_count).map(Effects::one);
49            }
50            (
51                IndirectCall,
52                CPUMessage::FunctionInfo {
53                    arity,
54                    start_address,
55                    local_count,
56                },
57            ) => {
58                return self
59                    .op_indirect_call(arity, start_address, local_count)
60                    .map(Effects::one);
61            }
62            (_, CPUMessage::FunctionInfo { .. }) => {
63                return Err(eyre::eyre!(
64                    "CPUMessage::FunctionInfo is only valid for call instructions"
65                ));
66            }
67            (_, CPUMessage::None) => {}
68        }
69
70        let outcome = match inst {
71            Span(file, start, end) => self.op_span(*file, *start, *end),
72            Label(_) | FunctionStart | FunctionEnd => Ok(StepOutcome::Continue),
73            Breakpoint => Ok(StepOutcome::Breakpoint),
74            Branch(target) => self.op_branch(*target),
75            ConditionalBranch(true_target, false_target) => {
76                self.op_conditional_branch(*true_target, *false_target)
77            }
78            Return(keep) => self.op_return(*keep),
79            Call(..) | IndirectCall => Err(eyre::eyre!("call requires CPUMessage::FunctionInfo")),
80            Halt => Ok(StepOutcome::Halt),
81            Print => Err(eyre::eyre!(
82                "Print must be handled via execute with CPUMessage::Print"
83            )),
84            LoadI32(addr) => self.op_load(*addr),
85            LoadI64(addr) => self.op_load(*addr),
86            LoadU32(addr) => self.op_load(*addr),
87            LoadU64(addr) => self.op_load(*addr),
88            LoadF32(addr) => self.op_load(*addr),
89            LoadF64(addr) => self.op_load(*addr),
90            LoadBool(addr) => self.op_load(*addr),
91            StoreI32(addr) => self.op_store(*addr),
92            StoreI64(addr) => self.op_store(*addr),
93            StoreU32(addr) => self.op_store(*addr),
94            StoreU64(addr) => self.op_store(*addr),
95            StoreF32(addr) => self.op_store(*addr),
96            StoreF64(addr) => self.op_store(*addr),
97            StoreBool(addr) => self.op_store(*addr),
98            Dup => self.op_dup(),
99            HeapAlloc(n_elements) => self.op_heap_alloc(*n_elements),
100            GetItem => self.op_get_item(),
101            HeapDealloc => self.op_heap_dealloc(),
102            HeapReserve => self.op_heap_reserve(),
103            HeapPush => self.op_heap_push(),
104            HeapLen => self.op_heap_len(),
105            ConstI32(v) | ConstI64(v) | ConstU32(v) | ConstU64(v) | ConstF32(v) | ConstF64(v)
106            | ConstBool(v) | ConstString(v) | ConstFunctionRef(v) | ConstHeapRef(v) => {
107                self.op_const(*v)
108            }
109            AddI32 => self.add_i32(),
110            AddI64 => self.add_i64(),
111            AddU32 => self.add_u32(),
112            AddU64 => self.add_u64(),
113            AddF32 => self.add_f32(),
114            AddF64 => self.add_f64(),
115            SubI32 => self.sub_i32(),
116            SubI64 => self.sub_i64(),
117            SubU32 => self.sub_u32(),
118            SubU64 => self.sub_u64(),
119            SubF32 => self.sub_f32(),
120            SubF64 => self.sub_f64(),
121            MulI32 => self.mul_i32(),
122            MulI64 => self.mul_i64(),
123            MulU32 => self.mul_u32(),
124            MulU64 => self.mul_u64(),
125            MulF32 => self.mul_f32(),
126            MulF64 => self.mul_f64(),
127            DivI32 => self.div_i32(),
128            DivI64 => self.div_i64(),
129            DivU32 => self.div_u32(),
130            DivU64 => self.div_u64(),
131            DivF32 => self.div_f32(),
132            DivF64 => self.div_f64(),
133            RemI32 => self.rem_i32(),
134            RemI64 => self.rem_i64(),
135            RemU32 => self.rem_u32(),
136            RemU64 => self.rem_u64(),
137            RemF32 => self.rem_f32(),
138            RemF64 => self.rem_f64(),
139            NegI32 => self.neg_i32(),
140            NegI64 => self.neg_i64(),
141            NegF32 => self.neg_f32(),
142            NegF64 => self.neg_f64(),
143            ShlI32 => self.shl_i32(),
144            ShlI64 => self.shl_i64(),
145            ShlU32 => self.shl_u32(),
146            ShlU64 => self.shl_u64(),
147            ShrI32 => self.shr_i32(),
148            ShrI64 => self.shr_i64(),
149            ShrU32 => self.shr_u32(),
150            ShrU64 => self.shr_u64(),
151            RolI32 => self.rol_i32(),
152            RolI64 => self.rol_i64(),
153            RolU32 => self.rol_u32(),
154            RolU64 => self.rol_u64(),
155            RorI32 => self.ror_i32(),
156            RorI64 => self.ror_i64(),
157            RorU32 => self.ror_u32(),
158            RorU64 => self.ror_u64(),
159            BitAndI32 => self.bitand_i32(),
160            BitAndI64 => self.bitand_i64(),
161            BitAndU32 => self.bitand_u32(),
162            BitAndU64 => self.bitand_u64(),
163            BitOrI32 => self.bitor_i32(),
164            BitOrI64 => self.bitor_i64(),
165            BitOrU32 => self.bitor_u32(),
166            BitOrU64 => self.bitor_u64(),
167            BitXorI32 => self.bitxor_i32(),
168            BitXorI64 => self.bitxor_i64(),
169            BitXorU32 => self.bitxor_u32(),
170            BitXorU64 => self.bitxor_u64(),
171            Not => self.op_not(),
172            And => self.op_and(),
173            Or => self.op_or(),
174            Xor => self.op_xor(),
175            EqI32 => self.eq_i32(),
176            EqI64 => self.eq_i64(),
177            EqU32 => self.eq_u32(),
178            EqU64 => self.eq_u64(),
179            EqF32 => self.eq_f32(),
180            EqF64 => self.eq_f64(),
181            NeI32 => self.ne_i32(),
182            NeI64 => self.ne_i64(),
183            NeU32 => self.ne_u32(),
184            NeU64 => self.ne_u64(),
185            NeF32 => self.ne_f32(),
186            NeF64 => self.ne_f64(),
187            LtI32 => self.lt_i32(),
188            LtI64 => self.lt_i64(),
189            LtU32 => self.lt_u32(),
190            LtU64 => self.lt_u64(),
191            LtF32 => self.lt_f32(),
192            LtF64 => self.lt_f64(),
193            GtI32 => self.gt_i32(),
194            GtI64 => self.gt_i64(),
195            GtU32 => self.gt_u32(),
196            GtU64 => self.gt_u64(),
197            GtF32 => self.gt_f32(),
198            GtF64 => self.gt_f64(),
199            LeI32 => self.le_i32(),
200            LeI64 => self.le_i64(),
201            LeU32 => self.le_u32(),
202            LeU64 => self.le_u64(),
203            LeF32 => self.le_f32(),
204            LeF64 => self.le_f64(),
205            GeI32 => self.ge_i32(),
206            GeI64 => self.ge_i64(),
207            GeU32 => self.ge_u32(),
208            GeU64 => self.ge_u64(),
209            GeF32 => self.ge_f32(),
210            GeF64 => self.ge_f64(),
211        }?;
212        Ok(Effects::one(outcome))
213    }
214}
215
216#[derive(Debug, Clone, PartialEq, vihaco::Message)]
217pub enum CPUMessage {
218    None,
219    /// Target metadata selected by the composite for the executing CPU device.
220    FunctionInfo {
221        arity: u32,
222        start_address: u32,
223        local_count: u32,
224    },
225    Print(String),
226}
227
228#[dispatch(instruction = RuntimeInstruction, message = CPUMessage, effect = StepOutcome)]
229impl CPU {
230    fn execute(
231        &mut self,
232        inst: &RuntimeInstruction,
233        msg: CPUMessage,
234    ) -> eyre::Result<Effects<StepOutcome>> {
235        self.execute_generated(inst, msg)
236    }
237}
238
239impl CPU {
240    pub fn op_span(&mut self, file: u32, start: u32, end: u32) -> eyre::Result<StepOutcome> {
241        self.span = (file, start, end);
242        Ok(StepOutcome::Continue)
243    }
244
245    pub fn op_branch(&mut self, target: u32) -> eyre::Result<StepOutcome> {
246        self.set_pending_pc(target);
247        Ok(StepOutcome::Continue)
248    }
249
250    pub fn op_conditional_branch(
251        &mut self,
252        true_target: u32,
253        false_target: u32,
254    ) -> eyre::Result<StepOutcome> {
255        let cond = self.stack_pop()?;
256        match canonical_bool(cond)? {
257            true => {
258                self.set_pending_pc(true_target);
259                Ok(StepOutcome::Continue)
260            }
261            false => {
262                self.set_pending_pc(false_target);
263                Ok(StepOutcome::Continue)
264            }
265        }
266    }
267
268    pub fn op_return(&mut self, keep: u32) -> eyre::Result<StepOutcome> {
269        let frame = *self.get_frame()?;
270        let available = self
271            .stack
272            .len()
273            .checked_sub(frame.operands_index())
274            .ok_or_else(|| eyre::eyre!("frame locals out of bounds"))?;
275        if available < keep as usize {
276            return Err(eyre::eyre!("not enough values to return"));
277        }
278
279        self.pop_frame()?;
280        // Collect return values before truncating
281        let top = self.stack.len() - keep as usize;
282        let return_values: Vec<Word> = self.stack[top..].to_vec();
283        self.stack.drain(frame.base..top);
284
285        if self.get_frame().is_err() {
286            // No more frames - program is returning
287            self.set_return_values(return_values);
288            Ok(StepOutcome::Return)
289        } else {
290            self.set_pending_pc(frame.ret_pc);
291            Ok(StepOutcome::Continue)
292        }
293    }
294
295    /// Set up an invocation from arguments already on the operand stack.
296    ///
297    /// Also used for program entry: push the entry arguments before calling this
298    /// method, then begin execution at the returned pending PC. `local_count`
299    /// includes parameters and comes from the composite's function metadata.
300    ///
301    /// # Errors
302    /// Returns an error for insufficient operand arguments, a local count
303    /// smaller than the arity, or an overflowing frame size or return address.
304    pub fn enter_function(
305        &mut self,
306        arity: u32,
307        target: u32,
308        local_count: u32,
309        function: Option<usize>,
310    ) -> eyre::Result<StepOutcome> {
311        self.require_operands(arity as usize)?;
312        self.ensure_local_count_is_at_least_arity(arity, local_count)?;
313        let base = self.stack.len() - arity as usize;
314        let end = base
315            .checked_add(local_count as usize)
316            .ok_or_else(|| eyre::eyre!("frame size overflow"))?;
317        let ret_pc = if self.frames.is_empty() {
318            0
319        } else {
320            self.current_pc
321                .checked_add(1)
322                .ok_or_else(|| eyre::eyre!("return address overflow"))?
323        };
324        self.stack.resize(end, 0);
325        self.push_frame(Frame {
326            base,
327            local_count: local_count as usize,
328            span: self.span,
329            function,
330            ret_pc,
331        });
332        self.set_pending_pc(target);
333        Ok(StepOutcome::Continue)
334    }
335
336    pub fn op_call(
337        &mut self,
338        arity: u32,
339        target: u32,
340        local_count: u32,
341    ) -> eyre::Result<StepOutcome> {
342        self.enter_function(arity, target, local_count, None)
343    }
344
345    pub fn op_indirect_call(
346        &mut self,
347        arity: u32,
348        target: u32,
349        local_count: u32,
350    ) -> eyre::Result<StepOutcome> {
351        // Only the function reference is an operand; metadata comes from the message.
352        let required = (arity as usize)
353            .checked_add(1)
354            .ok_or_else(|| eyre::eyre!("argument count overflow"))?;
355        self.require_operands(required)?;
356        let function = decode_function_ref(*self.stack_top()?);
357        self.stack_pop()?;
358        self.enter_function(arity, target, local_count, Some(function as usize))
359    }
360
361    fn op_load(&mut self, addr: u32) -> eyre::Result<StepOutcome> {
362        // addr should be local to frame.
363        let value = self.get_local(addr as usize)?;
364        self.stack_push(*value);
365        Ok(StepOutcome::Continue)
366    }
367
368    pub fn op_store(&mut self, addr: u32) -> Result<StepOutcome> {
369        let address = self.local_address(addr as usize)?;
370        let value: Word = self.stack_pop()?;
371        *self
372            .stack
373            .get_mut(address)
374            .ok_or_else(|| eyre::eyre!("local index out of bounds"))? = value;
375        Ok(StepOutcome::Continue)
376    }
377
378    pub fn op_dup(&mut self) -> Result<StepOutcome> {
379        let v = *self.stack_top()?;
380        self.stack.push(v);
381        Ok(StepOutcome::Continue)
382    }
383
384    pub fn op_heap_alloc(&mut self, n_elements: u32) -> Result<StepOutcome> {
385        let n: usize = n_elements as usize;
386        self.require_operands(n)?;
387        let start = self.stack.len() - n;
388        let values: Vec<Word> = self.stack.drain(start..).collect();
389        let heap_id = self.push_heap_object(values);
390        self.stack_push(encode_heap_ref(heap_id));
391        Ok(StepOutcome::Continue)
392    }
393
394    pub fn op_get_item(&mut self) -> Result<StepOutcome> {
395        let index = Self::heap_index(self.stack_pop()?)?;
396        let heap_id = decode_heap_ref(self.stack_pop()?);
397        let value = *self
398            .heap_object(heap_id)?
399            .get(index)
400            .ok_or_else(|| eyre::eyre!("heap index {} out of bounds", index))?;
401        self.stack_push(value);
402        Ok(StepOutcome::Continue)
403    }
404
405    pub fn op_heap_dealloc(&mut self) -> Result<StepOutcome> {
406        let id = decode_heap_ref(self.stack_pop()?);
407        self.dealloc_heap_object(id)?;
408        Ok(StepOutcome::Continue)
409    }
410
411    pub fn op_heap_reserve(&mut self) -> Result<StepOutcome> {
412        let capacity = decode_u64(self.stack_pop()?);
413        let capacity = usize::try_from(capacity)
414            .map_err(|_| eyre::eyre!("heap capacity {} does not fit in usize", capacity))?;
415        let heap_id = self.heap.reserve(capacity)?;
416        self.stack_push(encode_heap_ref(heap_id));
417        Ok(StepOutcome::Continue)
418    }
419
420    pub fn op_heap_push(&mut self) -> Result<StepOutcome> {
421        self.require_operands(2)?;
422        let value = self.stack_pop()?;
423        let heap_ref = self.stack_pop()?;
424        self.heap.push(decode_heap_ref(heap_ref), value)?;
425        self.stack_push(heap_ref);
426        Ok(StepOutcome::Continue)
427    }
428
429    pub fn op_heap_len(&mut self) -> Result<StepOutcome> {
430        let heap_id = decode_heap_ref(self.stack_pop()?);
431        let len = u64::try_from(self.heap_object(heap_id)?.len())?;
432        self.stack_push(encode_u64(len));
433        Ok(StepOutcome::Continue)
434    }
435
436    pub fn op_const(&mut self, v: Word) -> Result<StepOutcome> {
437        self.stack.push(v);
438        Ok(StepOutcome::Continue)
439    }
440
441    fn heap_index(value: Word) -> Result<usize> {
442        match decode_i64(value) {
443            index if index >= 0 => usize::try_from(index)
444                .map_err(|_| eyre::eyre!("heap index {} does not fit in usize", index)),
445            index => Err(eyre::eyre!(
446                "heap index must be non-negative, got {}",
447                index
448            )),
449        }
450    }
451}
452
453#[cfg(test)]
454#[allow(clippy::items_after_test_module)]
455mod tests {
456    use super::*;
457    use vihaco::{Effects, GeneratedComponent, frame::Frame, traits::StackMemory};
458    use vihaco_parser::Ident;
459
460    trait ExecuteInstruction {
461        fn execute_instruction(&mut self, instruction: RuntimeInstruction) -> Result<StepOutcome>;
462    }
463
464    impl ExecuteInstruction for CPU {
465        fn execute_instruction(&mut self, instruction: RuntimeInstruction) -> Result<StepOutcome> {
466            vihaco::expect_exactly_one_effect(GeneratedComponent::execute_generated(
467                self,
468                &instruction,
469                CPUMessage::None,
470            )?)
471        }
472    }
473
474    #[test]
475    fn cpu_generated_component_executes_instruction_without_message() {
476        let mut cpu = CPU::default();
477
478        GeneratedComponent::execute_generated(
479            &mut cpu,
480            &RuntimeInstruction::ConstI64(encode_i64(7)),
481            CPUMessage::None,
482        )
483        .unwrap();
484
485        assert_eq!(cpu.stack(), &vec![encode_i64(7)]);
486    }
487
488    #[test]
489    fn execute_instruction_applies_control_flow_without_action() {
490        let mut cpu = CPU::default();
491
492        let branch = cpu
493            .execute_instruction(RuntimeInstruction::Branch(9))
494            .unwrap();
495        assert_eq!(branch, StepOutcome::Continue);
496        assert_eq!(cpu.take_pending_pc(), Some(9));
497
498        let halt = cpu.execute_instruction(RuntimeInstruction::Halt).unwrap();
499        assert_eq!(halt, StepOutcome::Halt);
500        assert_eq!(cpu.take_pending_pc(), None);
501    }
502
503    #[test]
504    fn op_return_stores_terminal_values_in_runtime_state() {
505        let mut cpu = CPU::default();
506        cpu.push_frame(Frame {
507            base: 0,
508            local_count: 0,
509            span: (0, 0, 0),
510            function: None,
511            ret_pc: 0,
512        });
513        cpu.stack_push(encode_i64(7));
514
515        let outcome = cpu
516            .execute_instruction(RuntimeInstruction::Return(1))
517            .unwrap();
518
519        assert_eq!(outcome, StepOutcome::Return);
520        assert_eq!(cpu.return_values(), &[encode_i64(7)]);
521    }
522
523    #[test]
524    fn op_return_restores_callers_pc() {
525        let mut cpu = CPU {
526            current_pc: 10,
527            ..Default::default()
528        };
529        // Outer ("main") frame so the inner Return takes the Continue branch.
530        cpu.push_frame(Frame {
531            base: 0,
532            local_count: 0,
533            span: (0, 0, 0),
534            function: None,
535            ret_pc: 0,
536        });
537
538        // Caller would be executing `call 0, 100` at some PC; op_call sets
539        // pending_pc to the callee target.
540        GeneratedComponent::execute_generated(
541            &mut cpu,
542            &RuntimeInstruction::Call(0, 100),
543            CPUMessage::FunctionInfo {
544                arity: 0,
545                start_address: 100,
546                local_count: 0,
547            },
548        )
549        .unwrap();
550        assert_eq!(cpu.take_pending_pc(), Some(100));
551        assert_eq!(cpu.frames[1].ret_pc, 11);
552
553        // Callee returns immediately. pending_pc should be restored to the
554        // instruction after the call.
555        let outcome = cpu
556            .execute_instruction(RuntimeInstruction::Return(0))
557            .unwrap();
558        assert_eq!(outcome, StepOutcome::Continue);
559        assert_eq!(cpu.take_pending_pc(), Some(11),);
560    }
561
562    #[test]
563    fn op_indirect_call_records_return_pc_after_call_site() {
564        let mut cpu = CPU {
565            current_pc: 10,
566            ..Default::default()
567        };
568        cpu.push_frame(Frame {
569            base: 0,
570            local_count: 0,
571            span: (0, 0, 0),
572            function: None,
573            ret_pc: 0,
574        });
575
576        // The composite supplies arity/address/count; only FunctionRef is on the stack.
577        cpu.stack_push(encode_function_ref(7));
578        GeneratedComponent::execute_generated(
579            &mut cpu,
580            &RuntimeInstruction::IndirectCall,
581            CPUMessage::FunctionInfo {
582                arity: 0,
583                start_address: 100,
584                local_count: 0,
585            },
586        )
587        .unwrap();
588        assert_eq!(cpu.take_pending_pc(), Some(100));
589        assert_eq!(cpu.frames[1].ret_pc, 11);
590
591        let outcome = cpu
592            .execute_instruction(RuntimeInstruction::Return(0))
593            .unwrap();
594        assert_eq!(outcome, StepOutcome::Continue);
595        assert_eq!(cpu.take_pending_pc(), Some(11));
596    }
597
598    #[test]
599    fn op_return_keeps_bottom_of_frame_when_callee_leaves_scratch() {
600        let mut cpu = CPU::default();
601        // Outer frame so Return takes the Continue branch.
602        cpu.push_frame(Frame {
603            base: 0,
604            local_count: 0,
605            span: (0, 0, 0),
606            function: None,
607            ret_pc: 0,
608        });
609
610        // Simulate a callee frame holding [scratch_a, scratch_b, return_val]
611        // where only `return_val` (the top) should survive `ret 1`.
612        cpu.push_frame(Frame {
613            base: 0,
614            local_count: 0,
615            span: (0, 0, 0),
616            function: None,
617            ret_pc: 0,
618        });
619        cpu.stack_push(encode_i64(111)); // scratch — bottom of callee frame
620        cpu.stack_push(encode_i64(222)); // scratch — middle
621        cpu.stack_push(encode_i64(999)); // intended return value — top
622
623        let outcome = cpu
624            .execute_instruction(RuntimeInstruction::Return(1))
625            .unwrap();
626        assert_eq!(outcome, StepOutcome::Continue);
627
628        assert_eq!(cpu.stack(), &vec![encode_i64(999)],);
629    }
630
631    #[test]
632    fn op_heap_alloc_preserves_natural_push_order_and_returns_heap_ref() {
633        let mut cpu = CPU::default();
634        cpu.stack_push(encode_i64(10));
635        cpu.stack_push(encode_i64(20));
636        cpu.stack_push(encode_i64(30));
637
638        let outcome = cpu
639            .execute_instruction(RuntimeInstruction::HeapAlloc(3))
640            .unwrap();
641
642        assert_eq!(outcome, StepOutcome::Continue);
643        assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
644        assert_eq!(
645            cpu.heap.get(0).unwrap(),
646            &[encode_i64(10), encode_i64(20), encode_i64(30)]
647        );
648    }
649
650    #[test]
651    fn op_heap_alloc_supports_empty_heap_objects() {
652        let mut cpu = CPU::default();
653
654        let outcome = cpu
655            .execute_instruction(RuntimeInstruction::HeapAlloc(0))
656            .unwrap();
657
658        assert_eq!(outcome, StepOutcome::Continue);
659        assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
660        assert_eq!(cpu.heap.get(0).unwrap(), &[] as &[Word]);
661    }
662
663    #[test]
664    fn reserved_heap_appends_in_order_and_returns_same_reference() {
665        let mut cpu = CPU::default();
666        cpu.stack_push(encode_u32(2));
667        assert_eq!(
668            cpu.execute_instruction(RuntimeInstruction::HeapReserve)
669                .unwrap(),
670            StepOutcome::Continue
671        );
672        let heap_ref = *cpu.stack_top().unwrap();
673        assert_eq!(cpu.heap_object(decode_heap_ref(heap_ref)).unwrap(), &[]);
674
675        for value in [10, 20] {
676            cpu.stack_push(encode_i64(value));
677            assert_eq!(
678                cpu.execute_instruction(RuntimeInstruction::HeapPush)
679                    .unwrap(),
680                StepOutcome::Continue
681            );
682            assert_eq!(cpu.stack(), &[heap_ref]);
683        }
684        assert_eq!(
685            cpu.heap_object(decode_heap_ref(heap_ref)).unwrap(),
686            &[encode_i64(10), encode_i64(20)]
687        );
688
689        cpu.stack_push(encode_i64(30));
690        let err = cpu
691            .execute_instruction(RuntimeInstruction::HeapPush)
692            .unwrap_err();
693        assert!(err.to_string().contains("full"));
694        assert_eq!(
695            cpu.heap_object(decode_heap_ref(heap_ref)).unwrap(),
696            &[encode_i64(10), encode_i64(20)]
697        );
698
699        cpu.stack_push(heap_ref);
700        cpu.stack_push(encode_u32(1));
701        cpu.execute_instruction(RuntimeInstruction::GetItem)
702            .unwrap();
703        assert_eq!(cpu.stack(), &[encode_i64(20)]);
704    }
705
706    #[test]
707    fn heap_len_consumes_reference_and_preserves_other_stack_values() {
708        for values in [vec![], vec![encode_i64(10), encode_i64(20)]] {
709            let mut cpu = CPU::default();
710            let heap_id = cpu.push_heap_object(values.clone());
711            cpu.stack_push(encode_i64(42));
712            cpu.stack_push(encode_heap_ref(heap_id));
713
714            let outcome = cpu
715                .execute_instruction(RuntimeInstruction::HeapLen)
716                .unwrap();
717
718            assert_eq!(outcome, StepOutcome::Continue);
719            assert_eq!(
720                cpu.stack(),
721                &[encode_i64(42), encode_u64(values.len() as u64)]
722            );
723            assert_eq!(cpu.heap_object(heap_id).unwrap(), values);
724        }
725    }
726
727    #[test]
728    fn heap_len_reports_current_length_of_reserved_object() {
729        let mut cpu = CPU::default();
730        cpu.stack_push(encode_u64(3));
731        cpu.execute_instruction(RuntimeInstruction::HeapReserve)
732            .unwrap();
733        let heap_ref = *cpu.stack_top().unwrap();
734
735        cpu.execute_instruction(RuntimeInstruction::HeapLen)
736            .unwrap();
737        assert_eq!(cpu.stack_pop().unwrap(), encode_u64(0));
738
739        cpu.stack_push(heap_ref);
740        cpu.stack_push(encode_i64(10));
741        cpu.execute_instruction(RuntimeInstruction::HeapPush)
742            .unwrap();
743        cpu.execute_instruction(RuntimeInstruction::HeapLen)
744            .unwrap();
745        assert_eq!(cpu.stack(), &[encode_u64(1)]);
746    }
747
748    #[test]
749    fn heap_len_rejects_invalid_and_deallocated_objects() {
750        let mut cpu = CPU::default();
751        let heap_id = cpu.push_heap_object(vec![]);
752        cpu.dealloc_heap_object(heap_id).unwrap();
753        for (id, message) in [(heap_id, "deallocated"), (42, "invalid heap object id")] {
754            cpu.stack_push(encode_heap_ref(id));
755            let err = cpu
756                .execute_instruction(RuntimeInstruction::HeapLen)
757                .unwrap_err();
758            assert!(err.to_string().contains(message));
759        }
760    }
761
762    #[test]
763    fn heap_len_requires_operand() {
764        let mut cpu = CPU::default();
765        assert!(
766            cpu.execute_instruction(RuntimeInstruction::HeapLen)
767                .is_err()
768        );
769    }
770
771    #[test]
772    fn reserved_heap_get_item_checks_current_length() {
773        let mut cpu = CPU::default();
774        cpu.stack_push(encode_u64(3));
775        cpu.execute_instruction(RuntimeInstruction::HeapReserve)
776            .unwrap();
777        cpu.stack_push(encode_i64(10));
778        cpu.execute_instruction(RuntimeInstruction::HeapPush)
779            .unwrap();
780        cpu.stack_push(encode_u32(1));
781        let err = cpu
782            .execute_instruction(RuntimeInstruction::GetItem)
783            .unwrap_err();
784        assert!(err.to_string().contains("out of bounds"));
785    }
786
787    #[test]
788    fn heap_push_rejects_allocated_and_zero_capacity_objects() {
789        for instruction in [
790            RuntimeInstruction::HeapAlloc(1),
791            RuntimeInstruction::HeapReserve,
792        ] {
793            let mut cpu = CPU::default();
794            cpu.stack_push(encode_u64(0));
795            cpu.execute_instruction(instruction).unwrap();
796            cpu.stack_push(encode_i64(42));
797            let err = cpu
798                .execute_instruction(RuntimeInstruction::HeapPush)
799                .unwrap_err();
800            assert!(err.to_string().contains("full"));
801        }
802    }
803
804    #[test]
805    fn heap_push_rejects_invalid_and_deallocated_objects() {
806        let mut cpu = CPU::default();
807        cpu.stack_push(encode_u64(1));
808        cpu.execute_instruction(RuntimeInstruction::HeapReserve)
809            .unwrap();
810        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
811            .unwrap();
812        for (id, message) in [(0, "deallocated"), (42, "invalid heap object id")] {
813            cpu.stack_push(encode_heap_ref(id));
814            cpu.stack_push(encode_i64(1));
815            let err = cpu
816                .execute_instruction(RuntimeInstruction::HeapPush)
817                .unwrap_err();
818            assert!(err.to_string().contains(message));
819        }
820        cpu.stack_push(encode_u64(1));
821        cpu.execute_instruction(RuntimeInstruction::HeapReserve)
822            .unwrap();
823        assert_eq!(cpu.stack(), &[encode_heap_ref(0)]);
824        cpu.stack_push(encode_i64(7));
825        cpu.execute_instruction(RuntimeInstruction::HeapPush)
826            .unwrap();
827        assert_eq!(cpu.heap_object(0).unwrap(), &[encode_i64(7)]);
828    }
829
830    #[test]
831    fn heap_reserve_rejects_unrepresentable_allocation() {
832        let mut cpu = CPU::default();
833        cpu.stack_push(encode_u64(u64::MAX));
834        assert!(
835            cpu.execute_instruction(RuntimeInstruction::HeapReserve)
836                .is_err()
837        );
838        assert!(cpu.heap.is_empty());
839    }
840
841    #[test]
842    fn heap_append_instructions_require_operands() {
843        let mut cpu = CPU::default();
844        assert!(
845            cpu.execute_instruction(RuntimeInstruction::HeapReserve)
846                .is_err()
847        );
848        assert!(
849            cpu.execute_instruction(RuntimeInstruction::HeapPush)
850                .is_err()
851        );
852        cpu.stack_push(encode_heap_ref(0));
853        assert!(
854            cpu.execute_instruction(RuntimeInstruction::HeapPush)
855                .is_err()
856        );
857        assert_eq!(cpu.stack(), &[encode_heap_ref(0)]);
858    }
859
860    #[test]
861    fn op_get_item_reads_heap_value() {
862        let mut cpu = CPU::default();
863        cpu.stack_push(encode_i64(10));
864        cpu.stack_push(encode_i64(20));
865        cpu.stack_push(encode_i64(30));
866        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(3))
867            .unwrap();
868        cpu.stack_push(encode_u32(1));
869
870        let outcome = cpu
871            .execute_instruction(RuntimeInstruction::GetItem)
872            .unwrap();
873
874        assert_eq!(outcome, StepOutcome::Continue);
875        assert_eq!(cpu.stack(), &vec![encode_i64(20)]);
876    }
877
878    #[test]
879    fn op_get_item_rejects_non_heap_refs() {
880        let mut cpu = CPU::default();
881        cpu.stack_push(encode_i64(7));
882        cpu.stack_push(encode_u32(0));
883
884        let err = cpu
885            .execute_instruction(RuntimeInstruction::GetItem)
886            .unwrap_err();
887
888        assert!(err.to_string().contains("heap"));
889    }
890
891    #[test]
892    fn op_get_item_rejects_invalid_heap_ids() {
893        let mut cpu = CPU::default();
894        cpu.stack_push(encode_heap_ref(99));
895        cpu.stack_push(encode_u32(0));
896
897        let err = cpu
898            .execute_instruction(RuntimeInstruction::GetItem)
899            .unwrap_err();
900
901        assert!(err.to_string().contains("heap"));
902    }
903
904    #[test]
905    fn op_get_item_rejects_out_of_bounds_indices() {
906        let mut cpu = CPU::default();
907        cpu.stack_push(encode_i64(10));
908        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
909            .unwrap();
910        cpu.stack_push(encode_u32(3));
911
912        let err = cpu
913            .execute_instruction(RuntimeInstruction::GetItem)
914            .unwrap_err();
915
916        assert!(err.to_string().contains("index"));
917    }
918
919    #[test]
920    fn reset_clears_heap_allocations() {
921        let mut cpu = CPU::default();
922        cpu.stack_push(encode_i64(10));
923        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
924            .unwrap();
925
926        cpu.reset();
927
928        assert!(cpu.heap.is_empty());
929        assert!(cpu.stack().is_empty());
930    }
931
932    #[test]
933    fn execute_generated_dispatches_instruction_without_message() {
934        let mut cpu = CPU::default();
935        cpu.push_frame(Frame {
936            base: 0,
937            local_count: 0,
938            span: (0, 0, 0),
939            function: None,
940            ret_pc: 0,
941        });
942
943        let outcome = GeneratedComponent::execute_generated(
944            &mut cpu,
945            &RuntimeInstruction::ConstI64(encode_i64(99)),
946            CPUMessage::None,
947        )
948        .unwrap();
949
950        assert_eq!(outcome, Effects::one(StepOutcome::Continue));
951        assert_eq!(cpu.stack(), &vec![encode_i64(99)]);
952    }
953
954    #[test]
955    fn execute_generated_function_info_is_only_accepted_for_calls() {
956        let mut cpu = CPU::default();
957        cpu.push_frame(Frame {
958            base: 0,
959            local_count: 0,
960            span: (0, 0, 0),
961            function: None,
962            ret_pc: 0,
963        });
964
965        let outcome = GeneratedComponent::execute_generated(
966            &mut cpu,
967            &RuntimeInstruction::Label(Ident("label".to_owned())),
968            CPUMessage::FunctionInfo {
969                arity: 2,
970                start_address: 42,
971                local_count: 2,
972            },
973        )
974        .unwrap_err();
975
976        assert!(outcome.to_string().contains("only valid for call"));
977        assert!(cpu.stack().is_empty());
978    }
979
980    #[test]
981    fn execute_generated_print_returns_control_effect_and_pops_stack() {
982        let mut cpu = CPU::default();
983        cpu.push_frame(Frame {
984            base: 0,
985            local_count: 0,
986            span: (0, 0, 0),
987            function: None,
988            ret_pc: 0,
989        });
990        cpu.stack_push(encode_i64(42));
991
992        let outcome = GeneratedComponent::execute_generated(
993            &mut cpu,
994            &RuntimeInstruction::Print,
995            CPUMessage::Print("hello".into()),
996        )
997        .unwrap();
998
999        assert_eq!(outcome, Effects::one(StepOutcome::Continue));
1000        assert!(cpu.stack().is_empty());
1001    }
1002
1003    #[test]
1004    fn execute_generated_print_rejects_wrong_message() {
1005        let mut cpu = CPU::default();
1006        cpu.push_frame(Frame {
1007            base: 0,
1008            local_count: 0,
1009            span: (0, 0, 0),
1010            function: None,
1011            ret_pc: 0,
1012        });
1013        cpu.stack_push(encode_i64(42));
1014
1015        let err = GeneratedComponent::execute_generated(
1016            &mut cpu,
1017            &RuntimeInstruction::Print,
1018            CPUMessage::None,
1019        )
1020        .unwrap_err();
1021
1022        assert!(err.to_string().contains("Print requires"));
1023    }
1024
1025    #[test]
1026    fn op_heap_dealloc_marks_slot_dead() {
1027        let mut cpu = CPU::default();
1028        cpu.stack_push(encode_i64(42));
1029        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
1030            .unwrap();
1031        cpu.stack_push(encode_heap_ref(0));
1032
1033        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
1034            .unwrap();
1035
1036        assert!(
1037            cpu.heap
1038                .get(0)
1039                .unwrap_err()
1040                .to_string()
1041                .contains("deallocated")
1042        );
1043    }
1044
1045    #[test]
1046    fn op_heap_dealloc_slot_is_reused_on_next_alloc() {
1047        let mut cpu = CPU::default();
1048        cpu.stack_push(encode_i64(1));
1049        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
1050            .unwrap();
1051        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
1052            .unwrap();
1053
1054        cpu.stack_push(encode_i64(2));
1055        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
1056            .unwrap();
1057
1058        assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
1059        assert_eq!(cpu.heap.get(0).unwrap(), &[encode_i64(2)]);
1060    }
1061
1062    #[test]
1063    fn op_heap_dealloc_rejects_double_free() {
1064        let mut cpu = CPU::default();
1065        cpu.stack_push(encode_i64(1));
1066        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
1067            .unwrap();
1068        cpu.stack_push(encode_heap_ref(0));
1069        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
1070            .unwrap();
1071
1072        cpu.stack_push(encode_heap_ref(0));
1073        let err = cpu
1074            .execute_instruction(RuntimeInstruction::HeapDealloc)
1075            .unwrap_err();
1076
1077        assert!(err.to_string().contains("double-free"));
1078    }
1079
1080    #[test]
1081    fn op_heap_dealloc_rejects_invalid_id() {
1082        let mut cpu = CPU::default();
1083        cpu.stack_push(encode_heap_ref(99));
1084
1085        let err = cpu
1086            .execute_instruction(RuntimeInstruction::HeapDealloc)
1087            .unwrap_err();
1088
1089        assert!(err.to_string().contains("invalid heap object id"));
1090    }
1091
1092    #[test]
1093    fn reset_clears_free_list() {
1094        let mut cpu = CPU::default();
1095        cpu.stack_push(encode_i64(1));
1096        cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
1097            .unwrap();
1098        cpu.stack_push(encode_heap_ref(0));
1099        cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
1100            .unwrap();
1101
1102        cpu.reset();
1103
1104        assert!(cpu.heap.is_empty());
1105    }
1106
1107    #[test]
1108    fn typed_word_arithmetic_canonicalizes_narrow_results() {
1109        let mut cpu = CPU::default();
1110        cpu.stack_push(encode_i32(i32::MAX));
1111        cpu.stack_push(encode_i32(1));
1112        cpu.execute_instruction(RuntimeInstruction::AddI32).unwrap();
1113        assert_eq!(cpu.stack_pop().unwrap(), encode_i32(i32::MIN));
1114
1115        cpu.stack_push(encode_u32(u32::MAX));
1116        cpu.stack_push(encode_u32(1));
1117        cpu.execute_instruction(RuntimeInstruction::AddU32).unwrap();
1118        assert_eq!(cpu.stack_pop().unwrap(), 0);
1119
1120        cpu.stack_push(encode_f32(1.5));
1121        cpu.stack_push(encode_f32(2.0));
1122        cpu.execute_instruction(RuntimeInstruction::MulF32).unwrap();
1123        assert_eq!(decode_f32(cpu.stack_pop().unwrap()), 3.0);
1124    }
1125
1126    #[test]
1127    fn integer_division_and_remainder_report_errors() {
1128        let mut cpu = CPU::default();
1129        cpu.stack_push(encode_i64(7));
1130        cpu.stack_push(encode_i64(0));
1131        assert!(cpu.execute_instruction(RuntimeInstruction::DivI64).is_err());
1132
1133        cpu.stack_push(encode_u32(7));
1134        cpu.stack_push(encode_u32(0));
1135        assert!(cpu.execute_instruction(RuntimeInstruction::RemU32).is_err());
1136    }
1137
1138    #[test]
1139    fn boolean_words_must_be_canonical() {
1140        let mut cpu = CPU::default();
1141        cpu.stack_push(2u64);
1142        assert!(cpu.execute_instruction(RuntimeInstruction::Not).is_err());
1143
1144        cpu.stack_push(2u64);
1145        assert!(
1146            cpu.execute_instruction(RuntimeInstruction::ConditionalBranch(1, 2))
1147                .is_err()
1148        );
1149    }
1150}
1151
1152fn canonical_bool(value: Word) -> Result<bool> {
1153    match value {
1154        0 => Ok(false),
1155        1 => Ok(true),
1156        other => Err(eyre::eyre!("invalid boolean word {}", other)),
1157    }
1158}
1159
1160macro_rules! int_wrapping {
1161    ($($name:ident {
1162        decode: $decode:ident,
1163        encode: $encode:ident,
1164        operation: $op:ident
1165    });+ $(;)?) => {$ (
1166        #[inline(always)]
1167        fn $name(&mut self) -> Result<StepOutcome> {
1168            let rhs = $decode(self.stack_pop()?);
1169            let lhs = $decode(self.stack_pop()?);
1170            self.stack_push($encode(lhs.$op(rhs)));
1171            Ok(StepOutcome::Continue)
1172        }
1173    )+ };
1174}
1175
1176macro_rules! int_checked {
1177    ($($name:ident {
1178        decode: $decode:ident,
1179        encode: $encode:ident,
1180        operation: $op:ident,
1181        error: $message:literal
1182    });+ $(;)?) => {$ (
1183        #[inline(always)]
1184        fn $name(&mut self) -> Result<StepOutcome> {
1185            let rhs = $decode(self.stack_pop()?);
1186            let lhs = $decode(self.stack_pop()?);
1187            let value = lhs.$op(rhs).ok_or_else(|| eyre::eyre!($message))?;
1188            self.stack_push($encode(value));
1189            Ok(StepOutcome::Continue)
1190        }
1191    )+ };
1192}
1193
1194macro_rules! float_binary {
1195    ($($name:ident {
1196        decode: $decode:ident,
1197        encode: $encode:ident,
1198        operator: $op:tt
1199    });+ $(;)?) => {$ (
1200        #[inline(always)]
1201        fn $name(&mut self) -> Result<StepOutcome> {
1202            let rhs = $decode(self.stack_pop()?);
1203            let lhs = $decode(self.stack_pop()?);
1204            self.stack_push($encode(lhs $op rhs));
1205            Ok(StepOutcome::Continue)
1206        }
1207    )+ };
1208}
1209
1210macro_rules! shift {
1211    ($($name:ident {
1212        decode: $decode:ident,
1213        encode: $encode:ident,
1214        operation: $op:ident,
1215        count_mask: $mask:expr
1216    });+ $(;)?) => {$ (
1217        #[inline(always)]
1218        fn $name(&mut self) -> Result<StepOutcome> {
1219            let rhs = decode_u32(self.stack_pop()?);
1220            let lhs = $decode(self.stack_pop()?);
1221            self.stack_push($encode(lhs.$op(rhs & $mask)));
1222            Ok(StepOutcome::Continue)
1223        }
1224    )+ };
1225}
1226
1227macro_rules! rotate {
1228    ($($name:ident {
1229        decode: $decode:ident,
1230        encode: $encode:ident,
1231        operation: $op:ident
1232    });+ $(;)?) => {$ (
1233        #[inline(always)]
1234        fn $name(&mut self) -> Result<StepOutcome> {
1235            let rhs = decode_u32(self.stack_pop()?);
1236            let lhs = $decode(self.stack_pop()?);
1237            self.stack_push($encode(lhs.$op(rhs)));
1238            Ok(StepOutcome::Continue)
1239        }
1240    )+ };
1241}
1242
1243macro_rules! bitwise {
1244    ($($name:ident {
1245        decode: $decode:ident,
1246        encode: $encode:ident,
1247        operator: $op:tt
1248    });+ $(;)?) => {$ (
1249        #[inline(always)]
1250        fn $name(&mut self) -> Result<StepOutcome> {
1251            let rhs = $decode(self.stack_pop()?);
1252            let lhs = $decode(self.stack_pop()?);
1253            self.stack_push($encode(lhs $op rhs));
1254            Ok(StepOutcome::Continue)
1255        }
1256    )+ };
1257}
1258
1259macro_rules! compare {
1260    ($($name:ident {
1261        decode: $decode:ident,
1262        operator: $op:tt
1263    });+ $(;)?) => {$ (
1264        #[inline(always)]
1265        fn $name(&mut self) -> Result<StepOutcome> {
1266            let rhs = $decode(self.stack_pop()?);
1267            let lhs = $decode(self.stack_pop()?);
1268            self.stack_push(encode_bool(lhs $op rhs));
1269            Ok(StepOutcome::Continue)
1270        }
1271    )+ };
1272}
1273
1274impl CPU {
1275    int_wrapping! {
1276        add_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_add };
1277        add_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_add };
1278        add_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_add };
1279        add_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_add };
1280        sub_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_sub };
1281        sub_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_sub };
1282        sub_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_sub };
1283        sub_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_sub };
1284        mul_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_mul };
1285        mul_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_mul };
1286        mul_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_mul };
1287        mul_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_mul };
1288    }
1289    int_checked! {
1290        div_i32 { decode: decode_i32, encode: encode_i32, operation: checked_div, error: "integer division error" };
1291        div_i64 { decode: decode_i64, encode: encode_i64, operation: checked_div, error: "integer division error" };
1292        div_u32 { decode: decode_u32, encode: encode_u32, operation: checked_div, error: "integer division error" };
1293        div_u64 { decode: decode_u64, encode: encode_u64, operation: checked_div, error: "integer division error" };
1294        rem_i32 { decode: decode_i32, encode: encode_i32, operation: checked_rem, error: "integer remainder error" };
1295        rem_i64 { decode: decode_i64, encode: encode_i64, operation: checked_rem, error: "integer remainder error" };
1296        rem_u32 { decode: decode_u32, encode: encode_u32, operation: checked_rem, error: "integer remainder error" };
1297        rem_u64 { decode: decode_u64, encode: encode_u64, operation: checked_rem, error: "integer remainder error" };
1298    }
1299    float_binary! {
1300        add_f32 { decode: decode_f32, encode: encode_f32, operator: + };
1301        add_f64 { decode: decode_f64, encode: encode_f64, operator: + };
1302        sub_f32 { decode: decode_f32, encode: encode_f32, operator: - };
1303        sub_f64 { decode: decode_f64, encode: encode_f64, operator: - };
1304        mul_f32 { decode: decode_f32, encode: encode_f32, operator: * };
1305        mul_f64 { decode: decode_f64, encode: encode_f64, operator: * };
1306        div_f32 { decode: decode_f32, encode: encode_f32, operator: / };
1307        div_f64 { decode: decode_f64, encode: encode_f64, operator: / };
1308        rem_f32 { decode: decode_f32, encode: encode_f32, operator: % };
1309        rem_f64 { decode: decode_f64, encode: encode_f64, operator: % };
1310    }
1311
1312    #[inline(always)]
1313    fn neg_i32(&mut self) -> Result<StepOutcome> {
1314        let value = decode_i32(self.stack_pop()?).wrapping_neg();
1315        self.stack_push(encode_i32(value));
1316        Ok(StepOutcome::Continue)
1317    }
1318    #[inline(always)]
1319    fn neg_i64(&mut self) -> Result<StepOutcome> {
1320        let value = decode_i64(self.stack_pop()?).wrapping_neg();
1321        self.stack_push(encode_i64(value));
1322        Ok(StepOutcome::Continue)
1323    }
1324    #[inline(always)]
1325    fn neg_f32(&mut self) -> Result<StepOutcome> {
1326        let value = -decode_f32(self.stack_pop()?);
1327        self.stack_push(encode_f32(value));
1328        Ok(StepOutcome::Continue)
1329    }
1330    #[inline(always)]
1331    fn neg_f64(&mut self) -> Result<StepOutcome> {
1332        let value = -decode_f64(self.stack_pop()?);
1333        self.stack_push(encode_f64(value));
1334        Ok(StepOutcome::Continue)
1335    }
1336
1337    shift! {
1338        shl_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_shl, count_mask: 31 };
1339        shl_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_shl, count_mask: 63 };
1340        shl_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_shl, count_mask: 31 };
1341        shl_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_shl, count_mask: 63 };
1342        shr_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_shr, count_mask: 31 };
1343        shr_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_shr, count_mask: 63 };
1344        shr_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_shr, count_mask: 31 };
1345        shr_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_shr, count_mask: 63 };
1346    }
1347    rotate! {
1348        rol_i32 { decode: decode_i32, encode: encode_i32, operation: rotate_left };
1349        rol_i64 { decode: decode_i64, encode: encode_i64, operation: rotate_left };
1350        rol_u32 { decode: decode_u32, encode: encode_u32, operation: rotate_left };
1351        rol_u64 { decode: decode_u64, encode: encode_u64, operation: rotate_left };
1352        ror_i32 { decode: decode_i32, encode: encode_i32, operation: rotate_right };
1353        ror_i64 { decode: decode_i64, encode: encode_i64, operation: rotate_right };
1354        ror_u32 { decode: decode_u32, encode: encode_u32, operation: rotate_right };
1355        ror_u64 { decode: decode_u64, encode: encode_u64, operation: rotate_right };
1356    }
1357    bitwise! {
1358        bitand_i32 { decode: decode_i32, encode: encode_i32, operator: & };
1359        bitand_i64 { decode: decode_i64, encode: encode_i64, operator: & };
1360        bitand_u32 { decode: decode_u32, encode: encode_u32, operator: & };
1361        bitand_u64 { decode: decode_u64, encode: encode_u64, operator: & };
1362        bitor_i32 { decode: decode_i32, encode: encode_i32, operator: | };
1363        bitor_i64 { decode: decode_i64, encode: encode_i64, operator: | };
1364        bitor_u32 { decode: decode_u32, encode: encode_u32, operator: | };
1365        bitor_u64 { decode: decode_u64, encode: encode_u64, operator: | };
1366        bitxor_i32 { decode: decode_i32, encode: encode_i32, operator: ^ };
1367        bitxor_i64 { decode: decode_i64, encode: encode_i64, operator: ^ };
1368        bitxor_u32 { decode: decode_u32, encode: encode_u32, operator: ^ };
1369        bitxor_u64 { decode: decode_u64, encode: encode_u64, operator: ^ };
1370    }
1371    compare! {
1372        eq_i32 { decode: decode_i32, operator: == };
1373        eq_i64 { decode: decode_i64, operator: == };
1374        eq_u32 { decode: decode_u32, operator: == };
1375        eq_u64 { decode: decode_u64, operator: == };
1376        eq_f32 { decode: decode_f32, operator: == };
1377        eq_f64 { decode: decode_f64, operator: == };
1378        ne_i32 { decode: decode_i32, operator: != };
1379        ne_i64 { decode: decode_i64, operator: != };
1380        ne_u32 { decode: decode_u32, operator: != };
1381        ne_u64 { decode: decode_u64, operator: != };
1382        ne_f32 { decode: decode_f32, operator: != };
1383        ne_f64 { decode: decode_f64, operator: != };
1384        lt_i32 { decode: decode_i32, operator: < };
1385        lt_i64 { decode: decode_i64, operator: < };
1386        lt_u32 { decode: decode_u32, operator: < };
1387        lt_u64 { decode: decode_u64, operator: < };
1388        lt_f32 { decode: decode_f32, operator: < };
1389        lt_f64 { decode: decode_f64, operator: < };
1390        gt_i32 { decode: decode_i32, operator: > };
1391        gt_i64 { decode: decode_i64, operator: > };
1392        gt_u32 { decode: decode_u32, operator: > };
1393        gt_u64 { decode: decode_u64, operator: > };
1394        gt_f32 { decode: decode_f32, operator: > };
1395        gt_f64 { decode: decode_f64, operator: > };
1396        le_i32 { decode: decode_i32, operator: <= };
1397        le_i64 { decode: decode_i64, operator: <= };
1398        le_u32 { decode: decode_u32, operator: <= };
1399        le_u64 { decode: decode_u64, operator: <= };
1400        le_f32 { decode: decode_f32, operator: <= };
1401        le_f64 { decode: decode_f64, operator: <= };
1402        ge_i32 { decode: decode_i32, operator: >= };
1403        ge_i64 { decode: decode_i64, operator: >= };
1404        ge_u32 { decode: decode_u32, operator: >= };
1405        ge_u64 { decode: decode_u64, operator: >= };
1406        ge_f32 { decode: decode_f32, operator: >= };
1407        ge_f64 { decode: decode_f64, operator: >= };
1408    }
1409
1410    fn op_not(&mut self) -> Result<StepOutcome> {
1411        let value = !canonical_bool(self.stack_pop()?)?;
1412        self.stack_push(encode_bool(value));
1413        Ok(StepOutcome::Continue)
1414    }
1415    fn op_and(&mut self) -> Result<StepOutcome> {
1416        let rhs = canonical_bool(self.stack_pop()?)?;
1417        let lhs = canonical_bool(self.stack_pop()?)?;
1418        self.stack_push(encode_bool(lhs && rhs));
1419        Ok(StepOutcome::Continue)
1420    }
1421    fn op_or(&mut self) -> Result<StepOutcome> {
1422        let rhs = canonical_bool(self.stack_pop()?)?;
1423        let lhs = canonical_bool(self.stack_pop()?)?;
1424        self.stack_push(encode_bool(lhs || rhs));
1425        Ok(StepOutcome::Continue)
1426    }
1427    fn op_xor(&mut self) -> Result<StepOutcome> {
1428        let rhs = canonical_bool(self.stack_pop()?)?;
1429        let lhs = canonical_bool(self.stack_pop()?)?;
1430        self.stack_push(encode_bool(lhs ^ rhs));
1431        Ok(StepOutcome::Continue)
1432    }
1433}