1use 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 (
48 _,
49 CPUMessage::FunctionInfo {
50 arity,
51 start_address,
52 },
53 ) => {
54 self.stack_push(arity);
55 self.stack_push(start_address);
56 }
57 (_, CPUMessage::None) => {}
58 }
59
60 let outcome = match inst {
61 Span(file, start, end) => self.op_span(*file, *start, *end),
62 Label(_) | FunctionStart | FunctionEnd => Ok(StepOutcome::Continue),
63 Breakpoint => Ok(StepOutcome::Breakpoint),
64 Branch(target) => self.op_branch(*target),
65 ConditionalBranch(true_target, false_target) => {
66 self.op_conditional_branch(*true_target, *false_target)
67 }
68 Return(keep) => self.op_return(*keep),
69 Call(arity, target) => self.op_call(*arity, *target),
70 IndirectCall => self.op_indirect_call(),
71 Halt => Ok(StepOutcome::Halt),
72 Print => Err(eyre::eyre!(
73 "Print must be handled via execute with CPUMessage::Print"
74 )),
75 LoadI32(addr) => self.op_load(*addr),
76 LoadI64(addr) => self.op_load(*addr),
77 LoadU32(addr) => self.op_load(*addr),
78 LoadU64(addr) => self.op_load(*addr),
79 LoadF32(addr) => self.op_load(*addr),
80 LoadF64(addr) => self.op_load(*addr),
81 LoadBool(addr) => self.op_load(*addr),
82 StoreI32(addr) => self.op_store(*addr),
83 StoreI64(addr) => self.op_store(*addr),
84 StoreU32(addr) => self.op_store(*addr),
85 StoreU64(addr) => self.op_store(*addr),
86 StoreF32(addr) => self.op_store(*addr),
87 StoreF64(addr) => self.op_store(*addr),
88 StoreBool(addr) => self.op_store(*addr),
89 Dup => self.op_dup(),
90 HeapAlloc(n_elements) => self.op_heap_alloc(*n_elements),
91 GetItem => self.op_get_item(),
92 HeapDealloc => self.op_heap_dealloc(),
93 ConstI32(v) | ConstI64(v) | ConstU32(v) | ConstU64(v) | ConstF32(v) | ConstF64(v)
94 | ConstBool(v) | ConstString(v) | ConstFunctionRef(v) | ConstHeapRef(v) => {
95 self.op_const(*v)
96 }
97 AddI32 => self.add_i32(),
98 AddI64 => self.add_i64(),
99 AddU32 => self.add_u32(),
100 AddU64 => self.add_u64(),
101 AddF32 => self.add_f32(),
102 AddF64 => self.add_f64(),
103 SubI32 => self.sub_i32(),
104 SubI64 => self.sub_i64(),
105 SubU32 => self.sub_u32(),
106 SubU64 => self.sub_u64(),
107 SubF32 => self.sub_f32(),
108 SubF64 => self.sub_f64(),
109 MulI32 => self.mul_i32(),
110 MulI64 => self.mul_i64(),
111 MulU32 => self.mul_u32(),
112 MulU64 => self.mul_u64(),
113 MulF32 => self.mul_f32(),
114 MulF64 => self.mul_f64(),
115 DivI32 => self.div_i32(),
116 DivI64 => self.div_i64(),
117 DivU32 => self.div_u32(),
118 DivU64 => self.div_u64(),
119 DivF32 => self.div_f32(),
120 DivF64 => self.div_f64(),
121 RemI32 => self.rem_i32(),
122 RemI64 => self.rem_i64(),
123 RemU32 => self.rem_u32(),
124 RemU64 => self.rem_u64(),
125 RemF32 => self.rem_f32(),
126 RemF64 => self.rem_f64(),
127 NegI32 => self.neg_i32(),
128 NegI64 => self.neg_i64(),
129 NegF32 => self.neg_f32(),
130 NegF64 => self.neg_f64(),
131 ShlI32 => self.shl_i32(),
132 ShlI64 => self.shl_i64(),
133 ShlU32 => self.shl_u32(),
134 ShlU64 => self.shl_u64(),
135 ShrI32 => self.shr_i32(),
136 ShrI64 => self.shr_i64(),
137 ShrU32 => self.shr_u32(),
138 ShrU64 => self.shr_u64(),
139 RolI32 => self.rol_i32(),
140 RolI64 => self.rol_i64(),
141 RolU32 => self.rol_u32(),
142 RolU64 => self.rol_u64(),
143 RorI32 => self.ror_i32(),
144 RorI64 => self.ror_i64(),
145 RorU32 => self.ror_u32(),
146 RorU64 => self.ror_u64(),
147 BitAndI32 => self.bitand_i32(),
148 BitAndI64 => self.bitand_i64(),
149 BitAndU32 => self.bitand_u32(),
150 BitAndU64 => self.bitand_u64(),
151 BitOrI32 => self.bitor_i32(),
152 BitOrI64 => self.bitor_i64(),
153 BitOrU32 => self.bitor_u32(),
154 BitOrU64 => self.bitor_u64(),
155 BitXorI32 => self.bitxor_i32(),
156 BitXorI64 => self.bitxor_i64(),
157 BitXorU32 => self.bitxor_u32(),
158 BitXorU64 => self.bitxor_u64(),
159 Not => self.op_not(),
160 And => self.op_and(),
161 Or => self.op_or(),
162 Xor => self.op_xor(),
163 EqI32 => self.eq_i32(),
164 EqI64 => self.eq_i64(),
165 EqU32 => self.eq_u32(),
166 EqU64 => self.eq_u64(),
167 EqF32 => self.eq_f32(),
168 EqF64 => self.eq_f64(),
169 NeI32 => self.ne_i32(),
170 NeI64 => self.ne_i64(),
171 NeU32 => self.ne_u32(),
172 NeU64 => self.ne_u64(),
173 NeF32 => self.ne_f32(),
174 NeF64 => self.ne_f64(),
175 LtI32 => self.lt_i32(),
176 LtI64 => self.lt_i64(),
177 LtU32 => self.lt_u32(),
178 LtU64 => self.lt_u64(),
179 LtF32 => self.lt_f32(),
180 LtF64 => self.lt_f64(),
181 GtI32 => self.gt_i32(),
182 GtI64 => self.gt_i64(),
183 GtU32 => self.gt_u32(),
184 GtU64 => self.gt_u64(),
185 GtF32 => self.gt_f32(),
186 GtF64 => self.gt_f64(),
187 LeI32 => self.le_i32(),
188 LeI64 => self.le_i64(),
189 LeU32 => self.le_u32(),
190 LeU64 => self.le_u64(),
191 LeF32 => self.le_f32(),
192 LeF64 => self.le_f64(),
193 GeI32 => self.ge_i32(),
194 GeI64 => self.ge_i64(),
195 GeU32 => self.ge_u32(),
196 GeU64 => self.ge_u64(),
197 GeF32 => self.ge_f32(),
198 GeF64 => self.ge_f64(),
199 }?;
200 Ok(Effects::one(outcome))
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, vihaco::Message)]
205pub enum CPUMessage {
206 None,
207 FunctionInfo { arity: u32, start_address: u32 },
208 Print(String),
209}
210
211#[dispatch(instruction = RuntimeInstruction, message = CPUMessage, effect = StepOutcome)]
212impl CPU {
213 fn execute(
214 &mut self,
215 inst: &RuntimeInstruction,
216 msg: CPUMessage,
217 ) -> eyre::Result<Effects<StepOutcome>> {
218 self.execute_generated(inst, msg)
219 }
220}
221
222impl CPU {
223 pub fn op_span(&mut self, file: u32, start: u32, end: u32) -> eyre::Result<StepOutcome> {
224 self.span = (file, start, end);
225 Ok(StepOutcome::Continue)
226 }
227
228 pub fn op_branch(&mut self, target: u32) -> eyre::Result<StepOutcome> {
229 self.set_pending_pc(target);
230 Ok(StepOutcome::Continue)
231 }
232
233 pub fn op_conditional_branch(
234 &mut self,
235 true_target: u32,
236 false_target: u32,
237 ) -> eyre::Result<StepOutcome> {
238 let cond = self
239 .stack
240 .pop()
241 .ok_or_else(|| eyre::eyre!("stack underflow"))?;
242 match canonical_bool(cond)? {
243 true => {
244 self.set_pending_pc(true_target);
245 Ok(StepOutcome::Continue)
246 }
247 false => {
248 self.set_pending_pc(false_target);
249 Ok(StepOutcome::Continue)
250 }
251 }
252 }
253
254 pub fn op_return(&mut self, keep: u32) -> eyre::Result<StepOutcome> {
255 let frame = self.pop_frame()?;
256 let frame_len = self
257 .stack
258 .len()
259 .checked_sub(frame.base)
260 .ok_or_else(|| eyre::eyre!("frame base out of bounds"))?;
261 if frame_len < keep as usize {
262 return Err(eyre::eyre!("not enough values to return"));
263 }
264
265 let top = self.stack.len() - keep as usize;
267 let return_values: Vec<Word> = self.stack[top..].to_vec();
268 self.stack.drain(frame.base..top);
269
270 if self.get_frame().is_err() {
271 self.set_return_values(return_values);
273 Ok(StepOutcome::Return)
274 } else {
275 self.set_pending_pc(frame.ret_pc);
276 Ok(StepOutcome::Continue)
277 }
278 }
279
280 pub fn op_call(&mut self, arity: u32, target: u32) -> eyre::Result<StepOutcome> {
281 if self.stack.len() < (arity as usize) {
282 return Err(eyre::eyre!(
283 "not enough arguments on stack to call function"
284 ));
285 }
286
287 let base = self.stack.len() - (arity as usize);
288 let frame = Frame {
289 base,
290 span: self.span,
291 function: None,
292 ret_pc: self.current_pc + 1,
293 };
294 self.push_frame(frame);
295 self.set_pending_pc(target);
296 Ok(StepOutcome::Continue)
297 }
298
299 pub fn op_indirect_call(&mut self) -> eyre::Result<StepOutcome> {
300 let target: u32 = self.stack_pop()?.try_into()?;
302 let arity: u32 = self.stack_pop()?.try_into()?;
303 let f = decode_function_ref(self.stack_pop()?);
304
305 if self.stack.len() < (arity as usize) {
306 return Err(eyre::eyre!(
307 "not enough arguments on stack to call function"
308 ));
309 }
310
311 let base = self.stack.len() - (arity as usize);
312 let frame = Frame {
313 base,
314 span: self.span,
315 function: Some(f as usize),
316 ret_pc: self.current_pc + 1,
317 };
318 self.push_frame(frame);
319 self.set_pending_pc(target);
320 Ok(StepOutcome::Continue)
321 }
322
323 fn op_load(&mut self, addr: u32) -> eyre::Result<StepOutcome> {
324 let value = self.get_local(addr as usize)?;
326 self.stack_push(*value);
327 Ok(StepOutcome::Continue)
328 }
329
330 pub fn op_store(&mut self, addr: u32) -> Result<StepOutcome> {
331 let v: Word = self.stack_pop()?;
332 log::debug!("store value {:?} at addr {}", v, addr);
333 *self.get_local_mut(addr as usize)? = v;
334 Ok(StepOutcome::Continue)
335 }
336
337 pub fn op_dup(&mut self) -> Result<StepOutcome> {
338 let v = *self.stack_top()?;
339 self.stack.push(v);
340 Ok(StepOutcome::Continue)
341 }
342
343 pub fn op_heap_alloc(&mut self, n_elements: u32) -> Result<StepOutcome> {
344 let n: usize = n_elements as usize;
345 if self.stack.len() < n {
346 return Err(eyre::eyre!("stack underflow"));
347 }
348 let start = self.stack.len() - n;
349 let values: Box<[Word]> = self.stack.drain(start..).collect();
350 let heap_id = self.push_heap_object(values);
351 self.stack_push(encode_heap_ref(heap_id));
352 Ok(StepOutcome::Continue)
353 }
354
355 pub fn op_get_item(&mut self) -> Result<StepOutcome> {
356 let index = Self::heap_index(self.stack_pop()?)?;
357 let heap_id = decode_heap_ref(self.stack_pop()?);
358 let value = *self
359 .heap_object(heap_id)?
360 .get(index)
361 .ok_or_else(|| eyre::eyre!("heap index {} out of bounds", index))?;
362 self.stack_push(value);
363 Ok(StepOutcome::Continue)
364 }
365
366 pub fn op_heap_dealloc(&mut self) -> Result<StepOutcome> {
367 let id = decode_heap_ref(self.stack_pop()?);
368 self.dealloc_heap_object(id)?;
369 Ok(StepOutcome::Continue)
370 }
371
372 pub fn op_const(&mut self, v: Word) -> Result<StepOutcome> {
373 self.stack.push(v);
374 Ok(StepOutcome::Continue)
375 }
376
377 fn heap_index(value: Word) -> Result<usize> {
378 match decode_i64(value) {
379 index if index >= 0 => usize::try_from(index)
380 .map_err(|_| eyre::eyre!("heap index {} does not fit in usize", index)),
381 index => Err(eyre::eyre!(
382 "heap index must be non-negative, got {}",
383 index
384 )),
385 }
386 }
387}
388
389#[cfg(test)]
390#[allow(clippy::items_after_test_module)]
391mod tests {
392 use super::*;
393 use vihaco::{Effects, GeneratedComponent, frame::Frame, traits::StackMemory};
394 use vihaco_parser::Ident;
395
396 trait ExecuteInstruction {
397 fn execute_instruction(&mut self, instruction: RuntimeInstruction) -> Result<StepOutcome>;
398 }
399
400 impl ExecuteInstruction for CPU {
401 fn execute_instruction(&mut self, instruction: RuntimeInstruction) -> Result<StepOutcome> {
402 vihaco::expect_exactly_one_effect(GeneratedComponent::execute_generated(
403 self,
404 &instruction,
405 CPUMessage::None,
406 )?)
407 }
408 }
409
410 #[test]
411 fn cpu_generated_component_executes_instruction_without_message() {
412 let mut cpu = CPU::default();
413
414 GeneratedComponent::execute_generated(
415 &mut cpu,
416 &RuntimeInstruction::ConstI64(encode_i64(7)),
417 CPUMessage::None,
418 )
419 .unwrap();
420
421 assert_eq!(cpu.stack(), &vec![encode_i64(7)]);
422 }
423
424 #[test]
425 fn execute_instruction_applies_control_flow_without_action() {
426 let mut cpu = CPU::default();
427
428 let branch = cpu
429 .execute_instruction(RuntimeInstruction::Branch(9))
430 .unwrap();
431 assert_eq!(branch, StepOutcome::Continue);
432 assert_eq!(cpu.take_pending_pc(), Some(9));
433
434 let halt = cpu.execute_instruction(RuntimeInstruction::Halt).unwrap();
435 assert_eq!(halt, StepOutcome::Halt);
436 assert_eq!(cpu.take_pending_pc(), None);
437 }
438
439 #[test]
440 fn op_return_stores_terminal_values_in_runtime_state() {
441 let mut cpu = CPU::default();
442 cpu.push_frame(Frame {
443 base: 0,
444 span: (0, 0, 0),
445 function: None,
446 ret_pc: 0,
447 });
448 cpu.stack_push(encode_i64(7));
449
450 let outcome = cpu
451 .execute_instruction(RuntimeInstruction::Return(1))
452 .unwrap();
453
454 assert_eq!(outcome, StepOutcome::Return);
455 assert_eq!(cpu.return_values(), &[encode_i64(7)]);
456 }
457
458 #[test]
459 fn op_return_restores_callers_pc() {
460 let mut cpu = CPU {
461 current_pc: 10,
462 ..Default::default()
463 };
464 cpu.push_frame(Frame {
466 base: 0,
467 span: (0, 0, 0),
468 function: None,
469 ret_pc: 0,
470 });
471
472 cpu.execute_instruction(RuntimeInstruction::Call(0, 100))
475 .unwrap();
476 assert_eq!(cpu.take_pending_pc(), Some(100));
477 assert_eq!(cpu.frames[1].ret_pc, 11);
478
479 let outcome = cpu
482 .execute_instruction(RuntimeInstruction::Return(0))
483 .unwrap();
484 assert_eq!(outcome, StepOutcome::Continue);
485 assert_eq!(cpu.take_pending_pc(), Some(11),);
486 }
487
488 #[test]
489 fn op_indirect_call_records_return_pc_after_call_site() {
490 let mut cpu = CPU {
491 current_pc: 10,
492 ..Default::default()
493 };
494 cpu.push_frame(Frame {
495 base: 0,
496 span: (0, 0, 0),
497 function: None,
498 ret_pc: 0,
499 });
500
501 cpu.stack_push(encode_function_ref(7));
503 cpu.stack_push(encode_u32(0));
504 cpu.stack_push(encode_u32(100));
505
506 cpu.execute_instruction(RuntimeInstruction::IndirectCall)
507 .unwrap();
508 assert_eq!(cpu.take_pending_pc(), Some(100));
509 assert_eq!(cpu.frames[1].ret_pc, 11);
510
511 let outcome = cpu
512 .execute_instruction(RuntimeInstruction::Return(0))
513 .unwrap();
514 assert_eq!(outcome, StepOutcome::Continue);
515 assert_eq!(cpu.take_pending_pc(), Some(11));
516 }
517
518 #[test]
519 fn op_return_keeps_bottom_of_frame_when_callee_leaves_scratch() {
520 let mut cpu = CPU::default();
521 cpu.push_frame(Frame {
523 base: 0,
524 span: (0, 0, 0),
525 function: None,
526 ret_pc: 0,
527 });
528
529 cpu.push_frame(Frame {
532 base: 0,
533 span: (0, 0, 0),
534 function: None,
535 ret_pc: 0,
536 });
537 cpu.stack_push(encode_i64(111)); cpu.stack_push(encode_i64(222)); cpu.stack_push(encode_i64(999)); let outcome = cpu
542 .execute_instruction(RuntimeInstruction::Return(1))
543 .unwrap();
544 assert_eq!(outcome, StepOutcome::Continue);
545
546 assert_eq!(cpu.stack(), &vec![encode_i64(999)],);
547 }
548
549 #[test]
550 fn op_heap_alloc_preserves_natural_push_order_and_returns_heap_ref() {
551 let mut cpu = CPU::default();
552 cpu.stack_push(encode_i64(10));
553 cpu.stack_push(encode_i64(20));
554 cpu.stack_push(encode_i64(30));
555
556 let outcome = cpu
557 .execute_instruction(RuntimeInstruction::HeapAlloc(3))
558 .unwrap();
559
560 assert_eq!(outcome, StepOutcome::Continue);
561 assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
562 assert_eq!(
563 cpu.heap.get(0).unwrap(),
564 &[encode_i64(10), encode_i64(20), encode_i64(30)]
565 );
566 }
567
568 #[test]
569 fn op_heap_alloc_supports_empty_heap_objects() {
570 let mut cpu = CPU::default();
571
572 let outcome = cpu
573 .execute_instruction(RuntimeInstruction::HeapAlloc(0))
574 .unwrap();
575
576 assert_eq!(outcome, StepOutcome::Continue);
577 assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
578 assert_eq!(cpu.heap.get(0).unwrap(), &[] as &[Word]);
579 }
580
581 #[test]
582 fn op_get_item_reads_heap_value() {
583 let mut cpu = CPU::default();
584 cpu.stack_push(encode_i64(10));
585 cpu.stack_push(encode_i64(20));
586 cpu.stack_push(encode_i64(30));
587 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(3))
588 .unwrap();
589 cpu.stack_push(encode_u32(1));
590
591 let outcome = cpu
592 .execute_instruction(RuntimeInstruction::GetItem)
593 .unwrap();
594
595 assert_eq!(outcome, StepOutcome::Continue);
596 assert_eq!(cpu.stack(), &vec![encode_i64(20)]);
597 }
598
599 #[test]
600 fn op_get_item_rejects_non_heap_refs() {
601 let mut cpu = CPU::default();
602 cpu.stack_push(encode_i64(7));
603 cpu.stack_push(encode_u32(0));
604
605 let err = cpu
606 .execute_instruction(RuntimeInstruction::GetItem)
607 .unwrap_err();
608
609 assert!(err.to_string().contains("heap"));
610 }
611
612 #[test]
613 fn op_get_item_rejects_invalid_heap_ids() {
614 let mut cpu = CPU::default();
615 cpu.stack_push(encode_heap_ref(99));
616 cpu.stack_push(encode_u32(0));
617
618 let err = cpu
619 .execute_instruction(RuntimeInstruction::GetItem)
620 .unwrap_err();
621
622 assert!(err.to_string().contains("heap"));
623 }
624
625 #[test]
626 fn op_get_item_rejects_out_of_bounds_indices() {
627 let mut cpu = CPU::default();
628 cpu.stack_push(encode_i64(10));
629 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
630 .unwrap();
631 cpu.stack_push(encode_u32(3));
632
633 let err = cpu
634 .execute_instruction(RuntimeInstruction::GetItem)
635 .unwrap_err();
636
637 assert!(err.to_string().contains("index"));
638 }
639
640 #[test]
641 fn reset_clears_heap_allocations() {
642 let mut cpu = CPU::default();
643 cpu.stack_push(encode_i64(10));
644 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
645 .unwrap();
646
647 cpu.reset();
648
649 assert!(cpu.heap.is_empty());
650 assert!(cpu.stack().is_empty());
651 }
652
653 #[test]
654 fn execute_generated_dispatches_instruction_without_message() {
655 let mut cpu = CPU::default();
656 cpu.push_frame(Frame {
657 base: 0,
658 span: (0, 0, 0),
659 function: None,
660 ret_pc: 0,
661 });
662
663 let outcome = GeneratedComponent::execute_generated(
664 &mut cpu,
665 &RuntimeInstruction::ConstI64(encode_i64(99)),
666 CPUMessage::None,
667 )
668 .unwrap();
669
670 assert_eq!(outcome, Effects::one(StepOutcome::Continue));
671 assert_eq!(cpu.stack(), &vec![encode_i64(99)]);
672 }
673
674 #[test]
675 fn execute_generated_function_info_pushes_arity_and_start_address() {
676 let mut cpu = CPU::default();
677 cpu.push_frame(Frame {
678 base: 0,
679 span: (0, 0, 0),
680 function: None,
681 ret_pc: 0,
682 });
683
684 let outcome = GeneratedComponent::execute_generated(
685 &mut cpu,
686 &RuntimeInstruction::Label(Ident("label".to_owned())),
687 CPUMessage::FunctionInfo {
688 arity: 2,
689 start_address: 42,
690 },
691 )
692 .unwrap();
693
694 assert_eq!(outcome, Effects::one(StepOutcome::Continue));
695 assert_eq!(cpu.stack(), &vec![encode_u32(2), encode_u32(42)]);
697 }
698
699 #[test]
700 fn execute_generated_print_returns_control_effect_and_pops_stack() {
701 let mut cpu = CPU::default();
702 cpu.push_frame(Frame {
703 base: 0,
704 span: (0, 0, 0),
705 function: None,
706 ret_pc: 0,
707 });
708 cpu.stack_push(encode_i64(42));
709
710 let outcome = GeneratedComponent::execute_generated(
711 &mut cpu,
712 &RuntimeInstruction::Print,
713 CPUMessage::Print("hello".into()),
714 )
715 .unwrap();
716
717 assert_eq!(outcome, Effects::one(StepOutcome::Continue));
718 assert!(cpu.stack().is_empty());
719 }
720
721 #[test]
722 fn execute_generated_print_rejects_wrong_message() {
723 let mut cpu = CPU::default();
724 cpu.push_frame(Frame {
725 base: 0,
726 span: (0, 0, 0),
727 function: None,
728 ret_pc: 0,
729 });
730 cpu.stack_push(encode_i64(42));
731
732 let err = GeneratedComponent::execute_generated(
733 &mut cpu,
734 &RuntimeInstruction::Print,
735 CPUMessage::None,
736 )
737 .unwrap_err();
738
739 assert!(err.to_string().contains("Print requires"));
740 }
741
742 #[test]
743 fn op_heap_dealloc_marks_slot_dead() {
744 let mut cpu = CPU::default();
745 cpu.stack_push(encode_i64(42));
746 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
747 .unwrap();
748 cpu.stack_push(encode_heap_ref(0));
749
750 cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
751 .unwrap();
752
753 assert!(
754 cpu.heap
755 .get(0)
756 .unwrap_err()
757 .to_string()
758 .contains("deallocated")
759 );
760 }
761
762 #[test]
763 fn op_heap_dealloc_slot_is_reused_on_next_alloc() {
764 let mut cpu = CPU::default();
765 cpu.stack_push(encode_i64(1));
766 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
767 .unwrap();
768 cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
769 .unwrap();
770
771 cpu.stack_push(encode_i64(2));
772 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
773 .unwrap();
774
775 assert_eq!(cpu.stack(), &vec![encode_heap_ref(0)]);
776 assert_eq!(cpu.heap.get(0).unwrap(), &[encode_i64(2)]);
777 }
778
779 #[test]
780 fn op_heap_dealloc_rejects_double_free() {
781 let mut cpu = CPU::default();
782 cpu.stack_push(encode_i64(1));
783 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
784 .unwrap();
785 cpu.stack_push(encode_heap_ref(0));
786 cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
787 .unwrap();
788
789 cpu.stack_push(encode_heap_ref(0));
790 let err = cpu
791 .execute_instruction(RuntimeInstruction::HeapDealloc)
792 .unwrap_err();
793
794 assert!(err.to_string().contains("double-free"));
795 }
796
797 #[test]
798 fn op_heap_dealloc_rejects_invalid_id() {
799 let mut cpu = CPU::default();
800 cpu.stack_push(encode_heap_ref(99));
801
802 let err = cpu
803 .execute_instruction(RuntimeInstruction::HeapDealloc)
804 .unwrap_err();
805
806 assert!(err.to_string().contains("invalid heap object id"));
807 }
808
809 #[test]
810 fn reset_clears_free_list() {
811 let mut cpu = CPU::default();
812 cpu.stack_push(encode_i64(1));
813 cpu.execute_instruction(RuntimeInstruction::HeapAlloc(1))
814 .unwrap();
815 cpu.stack_push(encode_heap_ref(0));
816 cpu.execute_instruction(RuntimeInstruction::HeapDealloc)
817 .unwrap();
818
819 cpu.reset();
820
821 assert!(cpu.heap.is_empty());
822 }
823
824 #[test]
825 fn typed_word_arithmetic_canonicalizes_narrow_results() {
826 let mut cpu = CPU::default();
827 cpu.stack_push(encode_i32(i32::MAX));
828 cpu.stack_push(encode_i32(1));
829 cpu.execute_instruction(RuntimeInstruction::AddI32).unwrap();
830 assert_eq!(cpu.stack_pop().unwrap(), encode_i32(i32::MIN));
831
832 cpu.stack_push(encode_u32(u32::MAX));
833 cpu.stack_push(encode_u32(1));
834 cpu.execute_instruction(RuntimeInstruction::AddU32).unwrap();
835 assert_eq!(cpu.stack_pop().unwrap(), 0);
836
837 cpu.stack_push(encode_f32(1.5));
838 cpu.stack_push(encode_f32(2.0));
839 cpu.execute_instruction(RuntimeInstruction::MulF32).unwrap();
840 assert_eq!(decode_f32(cpu.stack_pop().unwrap()), 3.0);
841 }
842
843 #[test]
844 fn integer_division_and_remainder_report_errors() {
845 let mut cpu = CPU::default();
846 cpu.stack_push(encode_i64(7));
847 cpu.stack_push(encode_i64(0));
848 assert!(cpu.execute_instruction(RuntimeInstruction::DivI64).is_err());
849
850 cpu.stack_push(encode_u32(7));
851 cpu.stack_push(encode_u32(0));
852 assert!(cpu.execute_instruction(RuntimeInstruction::RemU32).is_err());
853 }
854
855 #[test]
856 fn boolean_words_must_be_canonical() {
857 let mut cpu = CPU::default();
858 cpu.stack_push(2u64);
859 assert!(cpu.execute_instruction(RuntimeInstruction::Not).is_err());
860
861 cpu.stack_push(2u64);
862 assert!(
863 cpu.execute_instruction(RuntimeInstruction::ConditionalBranch(1, 2))
864 .is_err()
865 );
866 }
867}
868
869fn canonical_bool(value: Word) -> Result<bool> {
870 match value {
871 0 => Ok(false),
872 1 => Ok(true),
873 other => Err(eyre::eyre!("invalid boolean word {}", other)),
874 }
875}
876
877macro_rules! int_wrapping {
878 ($($name:ident {
879 decode: $decode:ident,
880 encode: $encode:ident,
881 operation: $op:ident
882 });+ $(;)?) => {$ (
883 #[inline(always)]
884 fn $name(&mut self) -> Result<StepOutcome> {
885 let rhs = $decode(self.stack_pop()?);
886 let lhs = $decode(self.stack_pop()?);
887 self.stack_push($encode(lhs.$op(rhs)));
888 Ok(StepOutcome::Continue)
889 }
890 )+ };
891}
892
893macro_rules! int_checked {
894 ($($name:ident {
895 decode: $decode:ident,
896 encode: $encode:ident,
897 operation: $op:ident,
898 error: $message:literal
899 });+ $(;)?) => {$ (
900 #[inline(always)]
901 fn $name(&mut self) -> Result<StepOutcome> {
902 let rhs = $decode(self.stack_pop()?);
903 let lhs = $decode(self.stack_pop()?);
904 let value = lhs.$op(rhs).ok_or_else(|| eyre::eyre!($message))?;
905 self.stack_push($encode(value));
906 Ok(StepOutcome::Continue)
907 }
908 )+ };
909}
910
911macro_rules! float_binary {
912 ($($name:ident {
913 decode: $decode:ident,
914 encode: $encode:ident,
915 operator: $op:tt
916 });+ $(;)?) => {$ (
917 #[inline(always)]
918 fn $name(&mut self) -> Result<StepOutcome> {
919 let rhs = $decode(self.stack_pop()?);
920 let lhs = $decode(self.stack_pop()?);
921 self.stack_push($encode(lhs $op rhs));
922 Ok(StepOutcome::Continue)
923 }
924 )+ };
925}
926
927macro_rules! shift {
928 ($($name:ident {
929 decode: $decode:ident,
930 encode: $encode:ident,
931 operation: $op:ident,
932 count_mask: $mask:expr
933 });+ $(;)?) => {$ (
934 #[inline(always)]
935 fn $name(&mut self) -> Result<StepOutcome> {
936 let rhs = decode_u32(self.stack_pop()?);
937 let lhs = $decode(self.stack_pop()?);
938 self.stack_push($encode(lhs.$op(rhs & $mask)));
939 Ok(StepOutcome::Continue)
940 }
941 )+ };
942}
943
944macro_rules! rotate {
945 ($($name:ident {
946 decode: $decode:ident,
947 encode: $encode:ident,
948 operation: $op:ident
949 });+ $(;)?) => {$ (
950 #[inline(always)]
951 fn $name(&mut self) -> Result<StepOutcome> {
952 let rhs = decode_u32(self.stack_pop()?);
953 let lhs = $decode(self.stack_pop()?);
954 self.stack_push($encode(lhs.$op(rhs)));
955 Ok(StepOutcome::Continue)
956 }
957 )+ };
958}
959
960macro_rules! bitwise {
961 ($($name:ident {
962 decode: $decode:ident,
963 encode: $encode:ident,
964 operator: $op:tt
965 });+ $(;)?) => {$ (
966 #[inline(always)]
967 fn $name(&mut self) -> Result<StepOutcome> {
968 let rhs = $decode(self.stack_pop()?);
969 let lhs = $decode(self.stack_pop()?);
970 self.stack_push($encode(lhs $op rhs));
971 Ok(StepOutcome::Continue)
972 }
973 )+ };
974}
975
976macro_rules! compare {
977 ($($name:ident {
978 decode: $decode:ident,
979 operator: $op:tt
980 });+ $(;)?) => {$ (
981 #[inline(always)]
982 fn $name(&mut self) -> Result<StepOutcome> {
983 let rhs = $decode(self.stack_pop()?);
984 let lhs = $decode(self.stack_pop()?);
985 self.stack_push(encode_bool(lhs $op rhs));
986 Ok(StepOutcome::Continue)
987 }
988 )+ };
989}
990
991impl CPU {
992 int_wrapping! {
993 add_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_add };
994 add_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_add };
995 add_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_add };
996 add_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_add };
997 sub_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_sub };
998 sub_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_sub };
999 sub_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_sub };
1000 sub_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_sub };
1001 mul_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_mul };
1002 mul_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_mul };
1003 mul_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_mul };
1004 mul_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_mul };
1005 }
1006 int_checked! {
1007 div_i32 { decode: decode_i32, encode: encode_i32, operation: checked_div, error: "integer division error" };
1008 div_i64 { decode: decode_i64, encode: encode_i64, operation: checked_div, error: "integer division error" };
1009 div_u32 { decode: decode_u32, encode: encode_u32, operation: checked_div, error: "integer division error" };
1010 div_u64 { decode: decode_u64, encode: encode_u64, operation: checked_div, error: "integer division error" };
1011 rem_i32 { decode: decode_i32, encode: encode_i32, operation: checked_rem, error: "integer remainder error" };
1012 rem_i64 { decode: decode_i64, encode: encode_i64, operation: checked_rem, error: "integer remainder error" };
1013 rem_u32 { decode: decode_u32, encode: encode_u32, operation: checked_rem, error: "integer remainder error" };
1014 rem_u64 { decode: decode_u64, encode: encode_u64, operation: checked_rem, error: "integer remainder error" };
1015 }
1016 float_binary! {
1017 add_f32 { decode: decode_f32, encode: encode_f32, operator: + };
1018 add_f64 { decode: decode_f64, encode: encode_f64, operator: + };
1019 sub_f32 { decode: decode_f32, encode: encode_f32, operator: - };
1020 sub_f64 { decode: decode_f64, encode: encode_f64, operator: - };
1021 mul_f32 { decode: decode_f32, encode: encode_f32, operator: * };
1022 mul_f64 { decode: decode_f64, encode: encode_f64, operator: * };
1023 div_f32 { decode: decode_f32, encode: encode_f32, operator: / };
1024 div_f64 { decode: decode_f64, encode: encode_f64, operator: / };
1025 rem_f32 { decode: decode_f32, encode: encode_f32, operator: % };
1026 rem_f64 { decode: decode_f64, encode: encode_f64, operator: % };
1027 }
1028
1029 #[inline(always)]
1030 fn neg_i32(&mut self) -> Result<StepOutcome> {
1031 let value = decode_i32(self.stack_pop()?).wrapping_neg();
1032 self.stack_push(encode_i32(value));
1033 Ok(StepOutcome::Continue)
1034 }
1035 #[inline(always)]
1036 fn neg_i64(&mut self) -> Result<StepOutcome> {
1037 let value = decode_i64(self.stack_pop()?).wrapping_neg();
1038 self.stack_push(encode_i64(value));
1039 Ok(StepOutcome::Continue)
1040 }
1041 #[inline(always)]
1042 fn neg_f32(&mut self) -> Result<StepOutcome> {
1043 let value = -decode_f32(self.stack_pop()?);
1044 self.stack_push(encode_f32(value));
1045 Ok(StepOutcome::Continue)
1046 }
1047 #[inline(always)]
1048 fn neg_f64(&mut self) -> Result<StepOutcome> {
1049 let value = -decode_f64(self.stack_pop()?);
1050 self.stack_push(encode_f64(value));
1051 Ok(StepOutcome::Continue)
1052 }
1053
1054 shift! {
1055 shl_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_shl, count_mask: 31 };
1056 shl_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_shl, count_mask: 63 };
1057 shl_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_shl, count_mask: 31 };
1058 shl_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_shl, count_mask: 63 };
1059 shr_i32 { decode: decode_i32, encode: encode_i32, operation: wrapping_shr, count_mask: 31 };
1060 shr_i64 { decode: decode_i64, encode: encode_i64, operation: wrapping_shr, count_mask: 63 };
1061 shr_u32 { decode: decode_u32, encode: encode_u32, operation: wrapping_shr, count_mask: 31 };
1062 shr_u64 { decode: decode_u64, encode: encode_u64, operation: wrapping_shr, count_mask: 63 };
1063 }
1064 rotate! {
1065 rol_i32 { decode: decode_i32, encode: encode_i32, operation: rotate_left };
1066 rol_i64 { decode: decode_i64, encode: encode_i64, operation: rotate_left };
1067 rol_u32 { decode: decode_u32, encode: encode_u32, operation: rotate_left };
1068 rol_u64 { decode: decode_u64, encode: encode_u64, operation: rotate_left };
1069 ror_i32 { decode: decode_i32, encode: encode_i32, operation: rotate_right };
1070 ror_i64 { decode: decode_i64, encode: encode_i64, operation: rotate_right };
1071 ror_u32 { decode: decode_u32, encode: encode_u32, operation: rotate_right };
1072 ror_u64 { decode: decode_u64, encode: encode_u64, operation: rotate_right };
1073 }
1074 bitwise! {
1075 bitand_i32 { decode: decode_i32, encode: encode_i32, operator: & };
1076 bitand_i64 { decode: decode_i64, encode: encode_i64, operator: & };
1077 bitand_u32 { decode: decode_u32, encode: encode_u32, operator: & };
1078 bitand_u64 { decode: decode_u64, encode: encode_u64, operator: & };
1079 bitor_i32 { decode: decode_i32, encode: encode_i32, operator: | };
1080 bitor_i64 { decode: decode_i64, encode: encode_i64, operator: | };
1081 bitor_u32 { decode: decode_u32, encode: encode_u32, operator: | };
1082 bitor_u64 { decode: decode_u64, encode: encode_u64, operator: | };
1083 bitxor_i32 { decode: decode_i32, encode: encode_i32, operator: ^ };
1084 bitxor_i64 { decode: decode_i64, encode: encode_i64, operator: ^ };
1085 bitxor_u32 { decode: decode_u32, encode: encode_u32, operator: ^ };
1086 bitxor_u64 { decode: decode_u64, encode: encode_u64, operator: ^ };
1087 }
1088 compare! {
1089 eq_i32 { decode: decode_i32, operator: == };
1090 eq_i64 { decode: decode_i64, operator: == };
1091 eq_u32 { decode: decode_u32, operator: == };
1092 eq_u64 { decode: decode_u64, operator: == };
1093 eq_f32 { decode: decode_f32, operator: == };
1094 eq_f64 { decode: decode_f64, operator: == };
1095 ne_i32 { decode: decode_i32, operator: != };
1096 ne_i64 { decode: decode_i64, operator: != };
1097 ne_u32 { decode: decode_u32, operator: != };
1098 ne_u64 { decode: decode_u64, operator: != };
1099 ne_f32 { decode: decode_f32, operator: != };
1100 ne_f64 { decode: decode_f64, operator: != };
1101 lt_i32 { decode: decode_i32, operator: < };
1102 lt_i64 { decode: decode_i64, operator: < };
1103 lt_u32 { decode: decode_u32, operator: < };
1104 lt_u64 { decode: decode_u64, operator: < };
1105 lt_f32 { decode: decode_f32, operator: < };
1106 lt_f64 { decode: decode_f64, operator: < };
1107 gt_i32 { decode: decode_i32, operator: > };
1108 gt_i64 { decode: decode_i64, operator: > };
1109 gt_u32 { decode: decode_u32, operator: > };
1110 gt_u64 { decode: decode_u64, operator: > };
1111 gt_f32 { decode: decode_f32, operator: > };
1112 gt_f64 { decode: decode_f64, operator: > };
1113 le_i32 { decode: decode_i32, operator: <= };
1114 le_i64 { decode: decode_i64, operator: <= };
1115 le_u32 { decode: decode_u32, operator: <= };
1116 le_u64 { decode: decode_u64, operator: <= };
1117 le_f32 { decode: decode_f32, operator: <= };
1118 le_f64 { decode: decode_f64, operator: <= };
1119 ge_i32 { decode: decode_i32, operator: >= };
1120 ge_i64 { decode: decode_i64, operator: >= };
1121 ge_u32 { decode: decode_u32, operator: >= };
1122 ge_u64 { decode: decode_u64, operator: >= };
1123 ge_f32 { decode: decode_f32, operator: >= };
1124 ge_f64 { decode: decode_f64, operator: >= };
1125 }
1126
1127 fn op_not(&mut self) -> Result<StepOutcome> {
1128 let value = !canonical_bool(self.stack_pop()?)?;
1129 self.stack_push(encode_bool(value));
1130 Ok(StepOutcome::Continue)
1131 }
1132 fn op_and(&mut self) -> Result<StepOutcome> {
1133 let rhs = canonical_bool(self.stack_pop()?)?;
1134 let lhs = canonical_bool(self.stack_pop()?)?;
1135 self.stack_push(encode_bool(lhs && rhs));
1136 Ok(StepOutcome::Continue)
1137 }
1138 fn op_or(&mut self) -> Result<StepOutcome> {
1139 let rhs = canonical_bool(self.stack_pop()?)?;
1140 let lhs = canonical_bool(self.stack_pop()?)?;
1141 self.stack_push(encode_bool(lhs || rhs));
1142 Ok(StepOutcome::Continue)
1143 }
1144 fn op_xor(&mut self) -> Result<StepOutcome> {
1145 let rhs = canonical_bool(self.stack_pop()?)?;
1146 let lhs = canonical_bool(self.stack_pop()?)?;
1147 self.stack_push(encode_bool(lhs ^ rhs));
1148 Ok(StepOutcome::Continue)
1149 }
1150}