1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::bytecode::{Chunk, Instruction, Opcode, Value};
5
6use super::fault::Fault;
7use super::frame::Frame;
8use super::native::NativeTable;
9use super::result::VmResult;
10
11pub const MAX_CALL_DEPTH: usize = 4096;
17
18pub struct Vm {
26 chunk: Arc<Chunk>,
27 natives: Arc<NativeTable>,
28 frames: Vec<Frame>,
29 instructions_executed: u64,
32}
33
34impl Vm {
35 pub fn new(chunk: Arc<Chunk>, natives: Arc<NativeTable>, function: u32, args: &[Value]) -> Result<Self, Fault> {
40 let def = chunk
41 .function(function)
42 .ok_or(Fault::BadFunction { index: function, table_size: chunk.functions.len() as u32 })?;
43 let mut frame = Frame::new(function, def.num_registers, None);
44 frame.pc = def.entry as usize;
45 for (i, arg) in args.iter().enumerate().take(def.arity as usize) {
54 match frame.registers.get_mut(i) {
55 Some(slot) => *slot = arg.clone(),
56 None => {
57 return Err(Fault::RegisterOutOfRange {
58 reg: i as u8,
59 frame_size: def.num_registers,
60 })
61 }
62 }
63 }
64 Ok(Vm { chunk, natives, frames: vec![frame], instructions_executed: 0 })
65 }
66
67 pub fn instructions_executed(&self) -> u64 {
68 self.instructions_executed
69 }
70
71 pub fn current_function(&self) -> u32 {
74 debug_assert!(!self.frames.is_empty(), "frames empty while running");
78 match self.frames.last() {
79 Some(frame) => frame.function,
80 None => 0,
81 }
82 }
83
84 pub fn chunk_arc(&self) -> Arc<Chunk> {
90 self.chunk.clone()
91 }
92
93 pub fn natives_arc(&self) -> Arc<NativeTable> {
97 self.natives.clone()
98 }
99
100 #[inline]
107 pub fn resume_with(&mut self, dest_reg: u8, value: Value) -> Result<(), Fault> {
108 self.set_reg(dest_reg, value)
109 }
110
111 pub fn current_pc(&self) -> Option<usize> {
113 self.frames.last().map(|f| f.pc)
114 }
115
116 pub fn current_num_registers(&self) -> Option<u8> {
118 self.frames.last().map(|f| f.registers.len() as u8)
119 }
120
121 pub fn top_registers(&self) -> Option<&[Value]> {
123 self.frames.last().map(|f| f.registers.as_slice())
124 }
125
126 pub fn top_registers_mut(&mut self) -> Option<&mut [Value]> {
128 self.frames.last_mut().map(|f| f.registers.as_mut_slice())
129 }
130
131 pub fn set_pc(&mut self, pc: usize) {
133 if let Some(frame) = self.frames.last_mut() {
134 frame.pc = pc;
135 }
136 }
137
138 pub fn set_register(&mut self, reg: u8, value: Value) -> Result<(), Fault> {
140 self.set_reg(reg, value)
141 }
142
143 pub fn return_value(&mut self, value: Value) -> Result<Option<VmResult>, Fault> {
145 self.pop_frame(value)
146 }
147
148 #[inline]
151 fn current(&mut self) -> Result<&mut Frame, Fault> {
152 debug_assert!(!self.frames.is_empty(), "frames empty while running");
153 self.frames
154 .last_mut()
155 .ok_or(Fault::Invariant("empty frame stack while running"))
156 }
157
158 #[inline]
159 fn get_reg(&self, reg: u8) -> Result<Value, Fault> {
160 let frame = self
161 .frames
162 .last()
163 .ok_or(Fault::Invariant("empty frame stack while running"))?;
164 frame
165 .registers
166 .get(reg as usize)
167 .cloned()
168 .ok_or(Fault::RegisterOutOfRange {
169 reg,
170 frame_size: frame.registers.len() as u8,
171 })
172 }
173
174 #[inline]
175 fn set_reg(&mut self, reg: u8, value: Value) -> Result<(), Fault> {
176 let frame = self.current()?;
177 let len = frame.registers.len() as u8;
178 match frame.registers.get_mut(reg as usize) {
179 Some(slot) => {
180 *slot = value;
181 Ok(())
182 }
183 None => Err(Fault::RegisterOutOfRange { reg, frame_size: len }),
184 }
185 }
186
187 #[inline]
198 fn fetch(&mut self) -> Result<Option<Instruction>, Fault> {
199 let pc = self.current()?.pc;
200 let instr = self.chunk.code.get(pc).copied();
201 if instr.is_some() {
202 self.current()?.pc = pc + 1;
203 }
204 Ok(instr)
205 }
206
207 fn numeric_binop(&mut self, op: Opcode, dst: u8, lhs: u8, rhs: u8) -> Result<(), Fault> {
208 let a = self.get_reg(lhs)?;
209 let b = self.get_reg(rhs)?;
210 let result = match (op, &a, &b) {
211 (Opcode::Add, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_add(*y)),
212 (Opcode::Add, _, _) => Value::Float(as_f64(&a)? + as_f64(&b)?),
213 (Opcode::Sub, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_sub(*y)),
214 (Opcode::Sub, _, _) => Value::Float(as_f64(&a)? - as_f64(&b)?),
215 (Opcode::Mul, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_mul(*y)),
216 (Opcode::Mul, _, _) => Value::Float(as_f64(&a)? * as_f64(&b)?),
217 (Opcode::Div, Value::Int(x), Value::Int(y)) => {
218 if *y == 0 {
219 return Err(Fault::DivideByZero);
220 }
221 Value::Int(x.wrapping_div(*y))
222 }
223 (Opcode::Div, _, _) => {
224 let denom = as_f64(&b)?;
225 Value::Float(as_f64(&a)? / denom)
226 }
227 (Opcode::Mod, Value::Int(x), Value::Int(y)) => {
228 if *y == 0 {
229 return Err(Fault::DivideByZero);
230 }
231 Value::Int(x.wrapping_rem(*y))
232 }
233 (Opcode::Eq, _, _) => Value::Bool(a == b),
234 (Opcode::Lt, Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
235 (Opcode::Lt, _, _) => Value::Bool(as_f64(&a)? < as_f64(&b)?),
236 (Opcode::Le, Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
237 (Opcode::Le, _, _) => Value::Bool(as_f64(&a)? <= as_f64(&b)?),
238 _ => {
239 return Err(Fault::Invariant(
240 "numeric_binop called with a non-arithmetic opcode",
241 ))
242 }
243 };
244 self.set_reg(dst, result)
245 }
246
247 pub fn run(&mut self, budget: u32) -> VmResult {
255 for _ in 0..budget {
256 self.instructions_executed += 1;
257 let instr = match self.fetch() {
258 Ok(Some(i)) => i,
259 Ok(None) => {
260 match self.pop_frame(Value::Unit) {
262 Ok(Some(result)) => return result,
263 Ok(None) => continue,
264 Err(fault) => return VmResult::Trap(fault),
265 }
266 }
267 Err(fault) => return VmResult::Trap(fault),
268 };
269
270 macro_rules! trap {
271 ($e:expr) => {
272 match $e {
273 Ok(v) => v,
274 Err(fault) => return VmResult::Trap(fault),
275 }
276 };
277 }
278
279 match instr.op {
280 Opcode::Halt => {
281 let v = trap!(self.get_reg(0));
282 return VmResult::Complete(v);
283 }
284 Opcode::Nop => {}
285 Opcode::LoadConst => {
286 let idx = instr.imm as u32;
287 let konst = match self.chunk.constant(idx) {
288 Some(v) => v.clone(),
289 None => {
290 return VmResult::Trap(Fault::BadConstant {
291 index: idx,
292 pool_size: self.chunk.constants.len() as u32,
293 })
294 }
295 };
296 trap!(self.set_reg(instr.a, konst));
297 }
298 Opcode::LoadImm => {
299 trap!(self.set_reg(instr.a, Value::Int(instr.imm as i64)));
300 }
301 Opcode::Move => {
302 let v = trap!(self.get_reg(instr.b));
303 trap!(self.set_reg(instr.a, v));
304 }
305 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Div | Opcode::Mod
306 | Opcode::Eq | Opcode::Lt | Opcode::Le => {
307 trap!(self.numeric_binop(instr.op, instr.a, instr.b, instr.c));
308 }
309 Opcode::Neg => {
310 let v = trap!(self.get_reg(instr.b));
311 let negated = match v {
312 Value::Int(x) => Value::Int(-x),
313 Value::Float(x) => Value::Float(-x),
314 other => {
315 return VmResult::Trap(Fault::TypeMismatch {
316 expected: "int or float",
317 got: other.type_name(),
318 })
319 }
320 };
321 trap!(self.set_reg(instr.a, negated));
322 }
323 Opcode::Jump => {
324 let frame = trap!(self.current());
325 let target = frame.pc as i64 + instr.imm as i64;
326 frame.pc = target as usize;
327 }
328 Opcode::Branch => {
329 let cond = trap!(self.get_reg(instr.a));
330 if !cond.is_truthy() {
331 let frame = trap!(self.current());
332 let target = frame.pc as i64 + instr.imm as i64;
333 frame.pc = target as usize;
334 }
335 }
336 Opcode::Call => {
337 let function = instr.imm as u32;
338 let argc = instr.b;
339 let dst = instr.a;
340 if self.frames.len() >= MAX_CALL_DEPTH {
341 return VmResult::Trap(Fault::CallStackOverflow { depth: self.frames.len() });
342 }
343 let def = match self.chunk.function(function) {
344 Some(d) => d.clone(),
345 None => {
346 return VmResult::Trap(Fault::BadFunction {
347 index: function,
348 table_size: self.chunk.functions.len() as u32,
349 })
350 }
351 };
352 let mut args = Vec::with_capacity(argc as usize);
353 for i in 0..argc {
354 args.push(trap!(self.get_reg(trap!(reg_at(dst, u16::from(i))))));
355 }
356 let mut new_frame = Frame::new(function, def.num_registers, Some(dst));
357 new_frame.pc = def.entry as usize;
358 for (i, a) in args.into_iter().enumerate().take(def.arity as usize) {
360 match new_frame.registers.get_mut(i) {
361 Some(slot) => *slot = a,
362 None => {
363 return VmResult::Trap(Fault::RegisterOutOfRange {
364 reg: i as u8,
365 frame_size: def.num_registers,
366 })
367 }
368 }
369 }
370 self.frames.push(new_frame);
371 }
372 Opcode::CallNative => {
373 let native_index = instr.imm as u32;
374 let argc = instr.b;
375 let dst = instr.a;
376 let native_fn = match self.natives.get(native_index) {
377 Some(f) => f.clone(),
378 None => {
379 return VmResult::Trap(Fault::BadNative {
380 index: native_index,
381 table_size: self.natives.len() as u32,
382 })
383 }
384 };
385 let mut args = Vec::with_capacity(argc as usize);
386 for i in 0..argc {
387 args.push(trap!(self.get_reg(trap!(reg_at(dst, u16::from(i))))));
388 }
389 match native_fn(&args) {
397 Ok(value) => trap!(self.set_reg(dst, value)),
398 Err(fault) => return VmResult::Trap(fault),
399 }
400 }
401 Opcode::Return => {
402 let v = trap!(self.get_reg(instr.a));
403 match self.pop_frame(v) {
404 Ok(Some(result)) => return result,
405 Ok(None) => {}
406 Err(fault) => return VmResult::Trap(fault),
407 }
408 }
409 Opcode::Spawn => {
410 let argc = instr.b;
411 let mut args = Vec::with_capacity(argc as usize);
412 for i in 0..argc {
413 args.push(trap!(
422 self.get_reg(trap!(reg_at(instr.a, u16::from(i) + 1)))
423 ));
424 }
425 return VmResult::Spawn { function: instr.imm as u32, args, dest_reg: instr.a };
426 }
427 Opcode::Yield => return VmResult::Yield,
428 Opcode::Sleep => {
429 let millis = trap!(self.get_reg(instr.a));
430 let ms = match millis.as_int() {
431 Some(ms) if ms >= 0 => ms as u64,
432 _ => {
433 return VmResult::Trap(Fault::TypeMismatch {
434 expected: "non-negative int",
435 got: millis.type_name(),
436 })
437 }
438 };
439 return VmResult::Sleep(Duration::from_millis(ms));
440 }
441 Opcode::Exit => {
442 let v = trap!(self.get_reg(instr.a));
443 return VmResult::Complete(v);
444 }
445 Opcode::SelfPid => {
446 return VmResult::SelfPid { dest_reg: instr.a };
447 }
448 Opcode::Send => {
449 let target = trap!(self.get_reg(instr.a));
450 let message = trap!(self.get_reg(instr.b));
451 let cap = match target.as_cap() {
452 Some(c) => c,
453 None => {
454 return VmResult::Trap(Fault::TypeMismatch {
455 expected: "cap",
456 got: target.type_name(),
457 })
458 }
459 };
460 if message.as_message().is_none() {
461 return VmResult::Trap(Fault::TypeMismatch {
462 expected: "message",
463 got: message.type_name(),
464 });
465 }
466 return VmResult::Send {
467 target_cap: cap,
468 message,
469 };
470 }
471 Opcode::Receive => {
472 return VmResult::Receive {
473 dest_reg: instr.a,
474 timeout: None,
475 match_tag: None,
476 };
477 }
478 Opcode::ReceiveTimeout => {
479 let millis = trap!(self.get_reg(instr.b));
480 let ms = match millis.as_int() {
481 Some(n) if n >= 0 => n as u64,
482 _ => 0,
483 };
484 return VmResult::Receive {
485 dest_reg: instr.a,
486 timeout: Some(Duration::from_millis(ms)),
487 match_tag: None,
488 };
489 }
490 Opcode::ReceiveMatch => {
491 let tag_v = trap!(self.get_reg(instr.b));
492 let tag = match tag_from_value(&tag_v) {
493 Ok(t) => t,
494 Err(f) => return VmResult::Trap(f),
495 };
496 return VmResult::Receive {
497 dest_reg: instr.a,
498 timeout: None,
499 match_tag: Some(tag),
500 };
501 }
502 Opcode::ReceiveMatchImm => {
503 let tag = match u16::try_from(instr.imm) {
504 Ok(t) if instr.imm >= 0 => t,
505 _ => {
506 return VmResult::Trap(Fault::TypeMismatch {
507 expected: "tag u16",
508 got: "imm-out-of-range",
509 })
510 }
511 };
512 return VmResult::Receive {
513 dest_reg: instr.a,
514 timeout: None,
515 match_tag: Some(tag),
516 };
517 }
518 Opcode::Ask => {
519 let target = trap!(self.get_reg(instr.b));
520 let request = trap!(self.get_reg(instr.c));
521 let cap = match target.as_cap() {
522 Some(c) => c,
523 None => {
524 return VmResult::Trap(Fault::TypeMismatch {
525 expected: "cap",
526 got: target.type_name(),
527 })
528 }
529 };
530 if request.as_message().is_none() {
531 return VmResult::Trap(Fault::TypeMismatch {
532 expected: "message",
533 got: request.type_name(),
534 });
535 }
536 return VmResult::Ask {
537 dest_reg: instr.a,
538 target_cap: cap,
539 request,
540 timeout: None,
541 };
542 }
543 Opcode::AskTimeout => {
544 let target = trap!(self.get_reg(instr.b));
545 let request = trap!(self.get_reg(instr.c));
546 let millis_reg = match u8::try_from(instr.imm) {
547 Ok(r) => r,
548 Err(_) => {
549 return VmResult::Trap(Fault::TypeMismatch {
550 expected: "millis register",
551 got: "imm-out-of-range",
552 })
553 }
554 };
555 let millis = trap!(self.get_reg(millis_reg));
556 let ms = match millis.as_int() {
557 Some(n) if n >= 0 => n as u64,
558 _ => 0,
559 };
560 let cap = match target.as_cap() {
561 Some(c) => c,
562 None => {
563 return VmResult::Trap(Fault::TypeMismatch {
564 expected: "cap",
565 got: target.type_name(),
566 })
567 }
568 };
569 if request.as_message().is_none() {
570 return VmResult::Trap(Fault::TypeMismatch {
571 expected: "message",
572 got: request.type_name(),
573 });
574 }
575 return VmResult::Ask {
576 dest_reg: instr.a,
577 target_cap: cap,
578 request,
579 timeout: Some(Duration::from_millis(ms)),
580 };
581 }
582 Opcode::Monitor => {
583 let target = trap!(self.get_reg(instr.b));
584 let cap = match target.as_cap() {
585 Some(c) => c,
586 None => {
587 return VmResult::Trap(Fault::TypeMismatch {
588 expected: "cap",
589 got: target.type_name(),
590 })
591 }
592 };
593 return VmResult::Monitor {
594 dest_reg: instr.a,
595 target_cap: cap,
596 };
597 }
598 Opcode::Demonitor => {
599 return VmResult::Demonitor {
600 monitor_reg: instr.a,
601 };
602 }
603 Opcode::Link => {
604 let target = trap!(self.get_reg(instr.b));
605 let cap = match target.as_cap() {
606 Some(c) => c,
607 None => {
608 return VmResult::Trap(Fault::TypeMismatch {
609 expected: "cap",
610 got: target.type_name(),
611 })
612 }
613 };
614 return VmResult::Link {
615 dest_reg: instr.a,
616 target_cap: cap,
617 };
618 }
619 Opcode::Unlink => {
620 return VmResult::Unlink {
621 link_reg: instr.a,
622 };
623 }
624 Opcode::Trap => return VmResult::Trap(Fault::Explicit(instr.imm)),
625 }
626 }
627 VmResult::Yield
628 }
629
630 fn pop_frame(&mut self, value: Value) -> Result<Option<VmResult>, Fault> {
634 let finished = self
635 .frames
636 .pop()
637 .ok_or(Fault::Invariant("pop_frame on empty stack"))?;
638 match finished.dest_reg {
639 Some(dest) => {
640 if self.set_reg(dest, value).is_err() {
645 let frame_size = match self.frames.last() {
646 Some(f) => f.registers.len() as u8,
647 None => 0,
648 };
649 return Ok(Some(VmResult::Trap(Fault::RegisterOutOfRange {
650 reg: dest,
651 frame_size,
652 })));
653 }
654 Ok(None)
655 }
656 None => Ok(Some(VmResult::Complete(value))),
657 }
658 }
659}
660
661#[inline]
671fn reg_at(base: u8, offset: u16) -> Result<u8, Fault> {
672 match u8::try_from(u32::from(base) + u32::from(offset)) {
673 Ok(reg) => Ok(reg),
674 Err(_) => Err(Fault::RegisterIndexOverflow {
675 base,
676 offset: match u8::try_from(offset) {
677 Ok(o) => o,
678 Err(_) => u8::MAX,
679 },
680 }),
681 }
682}
683
684#[inline]
685fn as_f64(v: &Value) -> Result<f64, Fault> {
686 match v {
687 Value::Int(i) => Ok(*i as f64),
688 Value::Float(f) => Ok(*f),
689 other => Err(Fault::TypeMismatch { expected: "int or float", got: other.type_name() }),
690 }
691}
692
693#[inline]
695fn tag_from_value(v: &Value) -> Result<u16, Fault> {
696 match v.as_int() {
697 Some(i) if (0..=i64::from(u16::MAX)).contains(&i) => Ok(i as u16),
698 Some(_) => Err(Fault::TypeMismatch {
699 expected: "tag u16",
700 got: "int-out-of-range",
701 }),
702 None => Err(Fault::TypeMismatch {
703 expected: "int",
704 got: v.type_name(),
705 }),
706 }
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712 use crate::bytecode::builder::ChunkBuilder;
713
714 type TestResult = Result<(), Box<dyn std::error::Error>>;
715
716 #[test]
721 fn spawn_from_the_last_register_traps_instead_of_wrapping() -> TestResult {
722 let mut b = ChunkBuilder::new("t");
723 b.begin_function("main", 0, 2);
724 b.emit_spawn(255, 0, 1);
725 b.emit_return(0);
726 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), 0, &[])?;
727 match vm.run(10) {
728 VmResult::Trap(Fault::RegisterIndexOverflow {
729 base: 255,
730 offset: 1,
731 }) => Ok(()),
732 other => Err(format!("expected RegisterIndexOverflow, got {other:?}").into()),
733 }
734 }
735
736 #[test]
740 fn entering_a_function_with_too_few_registers_faults() -> TestResult {
741 let mut b = ChunkBuilder::new("t");
742 b.begin_function("main", 3, 1);
743 b.emit_return(0);
744 let args = [Value::Int(1), Value::Int(2), Value::Int(3)];
745 match Vm::new(Arc::new(b.finish()), NativeTable::empty(), 0, &args) {
746 Err(Fault::RegisterOutOfRange {
747 reg: 1,
748 frame_size: 1,
749 }) => Ok(()),
750 Err(e) => Err(format!("unexpected fault: {e}").into()),
751 Ok(_) => Err("three arguments cannot be loaded into one register".into()),
752 }
753 }
754
755 #[test]
756 fn calling_a_function_with_too_few_registers_traps() -> TestResult {
757 let mut b = ChunkBuilder::new("t");
758 let callee = b.begin_function("callee", 3, 1);
759 b.emit_return(0);
760 let main = b.begin_function("main", 0, 4);
761 b.emit_load_imm(0, 7);
762 b.emit_load_imm(1, 8);
763 b.emit_load_imm(2, 9);
764 b.emit_call(0, callee, 3);
765 b.emit_return(0);
766 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), main, &[])?;
767 match vm.run(50) {
768 VmResult::Trap(Fault::RegisterOutOfRange {
769 reg: 1,
770 frame_size: 1,
771 }) => Ok(()),
772 other => Err(format!("expected RegisterOutOfRange, got {other:?}").into()),
773 }
774 }
775
776 #[test]
779 fn gathering_up_to_the_last_register_still_works() -> TestResult {
780 let mut b = ChunkBuilder::new("t");
781 let callee = b.begin_function("callee", 1, 1);
782 b.emit_return(0);
783 let main = b.begin_function("main", 0, 255);
784 b.emit_load_imm(254, 5);
785 b.emit_call(254, callee, 1);
787 b.emit_return(254);
788 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), main, &[])?;
789 match vm.run(100) {
790 VmResult::Complete(_) => Ok(()),
791 other => Err(format!("expected completion, got {other:?}").into()),
792 }
793 }
794}