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