1use std::sync::Arc;
2use std::time::Duration;
3
4use crate::bytecode::{Chunk, Instruction, Opcode, Value};
5use crate::scheduler::FlowQuota;
6
7use super::fault::Fault;
8use super::frame::Frame;
9use super::native::{check_native_gate, NativeGate, NativeTable};
10use super::result::VmResult;
11
12pub const MAX_CALL_DEPTH: usize = 4096;
18
19pub struct Vm {
27 chunk: Arc<Chunk>,
28 natives: Arc<NativeTable>,
29 native_gate: NativeGate,
30 frames: Vec<Frame>,
31 instructions_executed: u64,
34 next_request_id: u64,
37 quota: Option<Arc<FlowQuota>>,
39}
40
41impl Vm {
42 pub fn new(chunk: Arc<Chunk>, natives: Arc<NativeTable>, function: u32, args: &[Value]) -> Result<Self, Fault> {
50 let gate = NativeGate::deny(natives.len());
51 Self::with_native_gate(chunk, natives, gate, function, args)
52 }
53
54 pub fn with_native_gate(
55 chunk: Arc<Chunk>,
56 natives: Arc<NativeTable>,
57 native_gate: NativeGate,
58 function: u32,
59 args: &[Value],
60 ) -> Result<Self, Fault> {
61 let def = chunk
62 .function(function)
63 .ok_or(Fault::BadFunction { index: function, table_size: chunk.functions.len() as u32 })?;
64 let mut frame = Frame::new(function, def.num_registers, None);
65 frame.pc = def.entry as usize;
66 for (i, arg) in args.iter().enumerate().take(def.arity as usize) {
67 match frame.registers.get_mut(i) {
68 Some(slot) => *slot = arg.clone(),
69 None => {
70 return Err(Fault::RegisterOutOfRange {
71 reg: i as u8,
72 frame_size: def.num_registers,
73 })
74 }
75 }
76 }
77 Ok(Vm {
78 chunk,
79 natives,
80 native_gate,
81 frames: vec![frame],
82 instructions_executed: 0,
83 next_request_id: 1,
84 quota: None,
85 })
86 }
87
88 pub fn set_quota(&mut self, quota: Arc<FlowQuota>) -> Result<(), Fault> {
93 for frame in &self.frames {
94 for slot in &frame.registers {
95 charge_heap_value("a, slot)?;
96 }
97 }
98 self.quota = Some(quota);
99 Ok(())
100 }
101
102 pub fn fresh_request_id(&mut self) -> u64 {
104 let id = self.next_request_id;
105 self.next_request_id = self.next_request_id.saturating_add(1);
106 if id == 0 {
107 return self.fresh_request_id();
108 }
109 id
110 }
111
112 pub fn instructions_executed(&self) -> u64 {
113 self.instructions_executed
114 }
115
116 pub fn current_function(&self) -> u32 {
119 debug_assert!(!self.frames.is_empty(), "frames empty while running");
123 match self.frames.last() {
124 Some(frame) => frame.function,
125 None => 0,
126 }
127 }
128
129 pub fn chunk_arc(&self) -> Arc<Chunk> {
135 self.chunk.clone()
136 }
137
138 pub fn natives_arc(&self) -> Arc<NativeTable> {
142 self.natives.clone()
143 }
144
145 #[inline]
152 pub fn resume_with(&mut self, dest_reg: u8, value: Value) -> Result<(), Fault> {
153 self.set_reg(dest_reg, value)
154 }
155
156 pub fn current_pc(&self) -> Option<usize> {
158 self.frames.last().map(|f| f.pc)
159 }
160
161 pub fn current_num_registers(&self) -> Option<u8> {
163 self.frames.last().map(|f| f.registers.len() as u8)
164 }
165
166 pub fn top_registers(&self) -> Option<&[Value]> {
168 self.frames.last().map(|f| f.registers.as_slice())
169 }
170
171 pub fn top_registers_mut(&mut self) -> Option<&mut [Value]> {
173 self.frames.last_mut().map(|f| f.registers.as_mut_slice())
174 }
175
176 pub fn set_pc(&mut self, pc: usize) {
178 if let Some(frame) = self.frames.last_mut() {
179 frame.pc = pc;
180 }
181 }
182
183 pub fn set_register(&mut self, reg: u8, value: Value) -> Result<(), Fault> {
185 self.set_reg(reg, value)
186 }
187
188 pub fn return_value(&mut self, value: Value) -> Result<Option<VmResult>, Fault> {
190 self.pop_frame(value)
191 }
192
193 #[inline]
196 fn current(&mut self) -> Result<&mut Frame, Fault> {
197 debug_assert!(!self.frames.is_empty(), "frames empty while running");
198 self.frames
199 .last_mut()
200 .ok_or(Fault::Invariant("empty frame stack while running"))
201 }
202
203 #[inline]
204 fn get_reg(&self, reg: u8) -> Result<Value, Fault> {
205 let frame = self
206 .frames
207 .last()
208 .ok_or(Fault::Invariant("empty frame stack while running"))?;
209 frame
210 .registers
211 .get(reg as usize)
212 .cloned()
213 .ok_or(Fault::RegisterOutOfRange {
214 reg,
215 frame_size: frame.registers.len() as u8,
216 })
217 }
218
219 #[inline]
220 fn set_reg(&mut self, reg: u8, value: Value) -> Result<(), Fault> {
221 self.charge_register_store(reg, &value)?;
222 let frame = self.current()?;
223 let len = frame.registers.len() as u8;
224 match frame.registers.get_mut(reg as usize) {
225 Some(slot) => {
226 *slot = value;
227 Ok(())
228 }
229 None => Err(Fault::RegisterOutOfRange { reg, frame_size: len }),
230 }
231 }
232
233 #[inline]
234 fn peek_reg(&self, reg: u8) -> Option<&Value> {
235 self.frames.last()?.registers.get(reg as usize)
236 }
237
238 fn charge_register_store(&self, reg: u8, value: &Value) -> Result<(), Fault> {
242 let Some(quota) = self.quota.as_ref() else {
243 return Ok(());
244 };
245 match (value, self.peek_reg(reg)) {
246 (Value::Str(s), Some(Value::Str(old))) if Arc::ptr_eq(s, old) => Ok(()),
247 (Value::Bytes(b), Some(Value::Bytes(old))) if Arc::ptr_eq(b, old) => Ok(()),
248 (Value::Str(_) | Value::Bytes(_), _) => charge_heap_value(quota, value),
249 _ => Ok(()),
250 }
251 }
252
253 #[inline]
264 fn fetch(&mut self) -> Result<Option<Instruction>, Fault> {
265 let pc = self.current()?.pc;
266 let instr = self.chunk.code.get(pc).copied();
267 if instr.is_some() {
268 self.current()?.pc = pc + 1;
269 }
270 Ok(instr)
271 }
272
273 fn numeric_binop(&mut self, op: Opcode, dst: u8, lhs: u8, rhs: u8) -> Result<(), Fault> {
274 let a = self.get_reg(lhs)?;
275 let b = self.get_reg(rhs)?;
276 let result = match (op, &a, &b) {
277 (Opcode::Add, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_add(*y)),
278 (Opcode::Add, _, _) => Value::Float(as_f64(&a)? + as_f64(&b)?),
279 (Opcode::Sub, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_sub(*y)),
280 (Opcode::Sub, _, _) => Value::Float(as_f64(&a)? - as_f64(&b)?),
281 (Opcode::Mul, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_mul(*y)),
282 (Opcode::Mul, _, _) => Value::Float(as_f64(&a)? * as_f64(&b)?),
283 (Opcode::Div, Value::Int(x), Value::Int(y)) => {
284 if *y == 0 {
285 return Err(Fault::DivideByZero);
286 }
287 Value::Int(x.wrapping_div(*y))
288 }
289 (Opcode::Div, _, _) => {
290 let denom = as_f64(&b)?;
291 Value::Float(as_f64(&a)? / denom)
292 }
293 (Opcode::Mod, Value::Int(x), Value::Int(y)) => {
294 if *y == 0 {
295 return Err(Fault::DivideByZero);
296 }
297 Value::Int(x.wrapping_rem(*y))
298 }
299 (Opcode::Eq, _, _) => Value::Bool(a == b),
300 (Opcode::Lt, Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
301 (Opcode::Lt, _, _) => Value::Bool(as_f64(&a)? < as_f64(&b)?),
302 (Opcode::Le, Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
303 (Opcode::Le, _, _) => Value::Bool(as_f64(&a)? <= as_f64(&b)?),
304 _ => {
305 return Err(Fault::Invariant(
306 "numeric_binop called with a non-arithmetic opcode",
307 ))
308 }
309 };
310 self.set_reg(dst, result)
311 }
312
313 pub fn run(&mut self, budget: u32) -> VmResult {
321 for _ in 0..budget {
322 self.instructions_executed += 1;
323 let instr = match self.fetch() {
324 Ok(Some(i)) => i,
325 Ok(None) => {
326 match self.pop_frame(Value::Unit) {
328 Ok(Some(result)) => return result,
329 Ok(None) => continue,
330 Err(fault) => return VmResult::Trap(fault),
331 }
332 }
333 Err(fault) => return VmResult::Trap(fault),
334 };
335
336 macro_rules! trap {
337 ($e:expr) => {
338 match $e {
339 Ok(v) => v,
340 Err(fault) => return VmResult::Trap(fault),
341 }
342 };
343 }
344
345 match instr.op {
346 Opcode::Halt => {
347 let v = trap!(self.get_reg(0));
348 return VmResult::Complete(v);
349 }
350 Opcode::Nop => {}
351 Opcode::LoadConst => {
352 let idx = instr.imm as u32;
353 let konst = match self.chunk.constant(idx) {
354 Some(v) => v.clone(),
355 None => {
356 return VmResult::Trap(Fault::BadConstant {
357 index: idx,
358 pool_size: self.chunk.constants.len() as u32,
359 })
360 }
361 };
362 trap!(self.set_reg(instr.a, konst));
363 }
364 Opcode::LoadImm => {
365 trap!(self.set_reg(instr.a, Value::Int(instr.imm as i64)));
366 }
367 Opcode::Move => {
368 let v = trap!(self.get_reg(instr.b));
369 trap!(self.set_reg(instr.a, v));
370 }
371 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Div | Opcode::Mod
372 | Opcode::Eq | Opcode::Lt | Opcode::Le => {
373 trap!(self.numeric_binop(instr.op, instr.a, instr.b, instr.c));
374 }
375 Opcode::Neg => {
376 let v = trap!(self.get_reg(instr.b));
377 let negated = match v {
378 Value::Int(x) => Value::Int(-x),
379 Value::Float(x) => Value::Float(-x),
380 other => {
381 return VmResult::Trap(Fault::TypeMismatch {
382 expected: "int or float",
383 got: other.type_name(),
384 })
385 }
386 };
387 trap!(self.set_reg(instr.a, negated));
388 }
389 Opcode::Jump => {
390 let frame = trap!(self.current());
391 let target = frame.pc as i64 + instr.imm as i64;
392 frame.pc = target as usize;
393 }
394 Opcode::Branch => {
395 let cond = trap!(self.get_reg(instr.a));
396 if !cond.is_truthy() {
397 let frame = trap!(self.current());
398 let target = frame.pc as i64 + instr.imm as i64;
399 frame.pc = target as usize;
400 }
401 }
402 Opcode::Call => {
403 let function = instr.imm as u32;
404 let argc = instr.b;
405 let dst = instr.a;
406 if self.frames.len() >= MAX_CALL_DEPTH {
407 return VmResult::Trap(Fault::CallStackOverflow { depth: self.frames.len() });
408 }
409 let def = match self.chunk.function(function) {
410 Some(d) => d.clone(),
411 None => {
412 return VmResult::Trap(Fault::BadFunction {
413 index: function,
414 table_size: self.chunk.functions.len() as u32,
415 })
416 }
417 };
418 let mut args = Vec::with_capacity(argc as usize);
419 for i in 0..argc {
420 args.push(trap!(self.get_reg(trap!(reg_at(dst, u16::from(i))))));
421 }
422 let mut new_frame = Frame::new(function, def.num_registers, Some(dst));
423 new_frame.pc = def.entry as usize;
424 for (i, a) in args.into_iter().enumerate().take(def.arity as usize) {
426 match new_frame.registers.get_mut(i) {
427 Some(slot) => *slot = a,
428 None => {
429 return VmResult::Trap(Fault::RegisterOutOfRange {
430 reg: i as u8,
431 frame_size: def.num_registers,
432 })
433 }
434 }
435 }
436 self.frames.push(new_frame);
437 }
438 Opcode::CallNative => {
439 let native_index = instr.imm as u32;
440 let argc = instr.b;
441 let dst = instr.a;
442 if let Err(err) = check_native_gate(&self.native_gate, &self.natives, native_index)
443 {
444 return VmResult::Trap(match err {
445 crate::vm::native::NativeCallError::IndexOutOfRange(index) => {
446 Fault::BadNative {
447 index,
448 table_size: self.natives.len() as u32,
449 }
450 }
451 other => Fault::NativeDenied(other.to_string()),
452 });
453 }
454 let native_fn = match self.natives.get(native_index) {
455 Some(f) => f.clone(),
456 None => {
457 return VmResult::Trap(Fault::BadNative {
458 index: native_index,
459 table_size: self.natives.len() as u32,
460 })
461 }
462 };
463 let mut args = Vec::with_capacity(argc as usize);
464 for i in 0..argc {
465 args.push(trap!(self.get_reg(trap!(reg_at(dst, u16::from(i))))));
466 }
467 match native_fn(&args) {
475 Ok(value) => trap!(self.set_reg(dst, value)),
476 Err(fault) => return VmResult::Trap(fault),
477 }
478 }
479 Opcode::Return => {
480 let v = trap!(self.get_reg(instr.a));
481 match self.pop_frame(v) {
482 Ok(Some(result)) => return result,
483 Ok(None) => {}
484 Err(fault) => return VmResult::Trap(fault),
485 }
486 }
487 Opcode::Spawn => {
488 let argc = instr.b;
489 let mut args = Vec::with_capacity(argc as usize);
490 for i in 0..argc {
491 args.push(trap!(
500 self.get_reg(trap!(reg_at(instr.a, u16::from(i) + 1)))
501 ));
502 }
503 return VmResult::Spawn {
504 function: instr.imm as u32,
505 args,
506 dest_reg: instr.a,
507 requested_rights: crate::bytecode::CapRights::from_u8(instr.c),
508 };
509 }
510 Opcode::Yield => return VmResult::Yield,
511 Opcode::Sleep => {
512 let millis = trap!(self.get_reg(instr.a));
513 let ms = match millis.as_int() {
514 Some(ms) if ms >= 0 => ms as u64,
515 _ => {
516 return VmResult::Trap(Fault::TypeMismatch {
517 expected: "non-negative int",
518 got: millis.type_name(),
519 })
520 }
521 };
522 return VmResult::Sleep(Duration::from_millis(ms));
523 }
524 Opcode::Exit => {
525 let v = trap!(self.get_reg(instr.a));
526 return VmResult::Complete(v);
527 }
528 Opcode::SelfPid => {
529 return VmResult::SelfPid { dest_reg: instr.a };
530 }
531 Opcode::Send => {
532 let target = trap!(self.get_reg(instr.a));
533 let message = trap!(self.get_reg(instr.b));
534 let cap = match target.as_cap() {
535 Some(c) => c,
536 None => {
537 return VmResult::Trap(Fault::TypeMismatch {
538 expected: "cap",
539 got: target.type_name(),
540 })
541 }
542 };
543 if message.as_message().is_none() {
544 return VmResult::Trap(Fault::TypeMismatch {
545 expected: "message",
546 got: message.type_name(),
547 });
548 }
549 return VmResult::Send {
550 target_cap: cap,
551 message,
552 };
553 }
554 Opcode::Receive => {
555 return VmResult::Receive {
556 dest_reg: instr.a,
557 timeout: None,
558 match_tag: None,
559 match_request_id: None,
560 };
561 }
562 Opcode::ReceiveTimeout => {
563 let millis = trap!(self.get_reg(instr.b));
564 let ms = match millis.as_int() {
565 Some(n) if n >= 0 => n as u64,
566 _ => 0,
567 };
568 return VmResult::Receive {
569 dest_reg: instr.a,
570 timeout: Some(Duration::from_millis(ms)),
571 match_tag: None,
572 match_request_id: None,
573 };
574 }
575 Opcode::ReceiveMatch => {
576 let tag_v = trap!(self.get_reg(instr.b));
577 let tag = match tag_from_value(&tag_v) {
578 Ok(t) => t,
579 Err(f) => return VmResult::Trap(f),
580 };
581 return VmResult::Receive {
582 dest_reg: instr.a,
583 timeout: None,
584 match_tag: Some(tag),
585 match_request_id: None,
586 };
587 }
588 Opcode::ReceiveMatchImm => {
589 let tag = match u16::try_from(instr.imm) {
590 Ok(t) if instr.imm >= 0 => t,
591 _ => {
592 return VmResult::Trap(Fault::TypeMismatch {
593 expected: "tag u16",
594 got: "imm-out-of-range",
595 })
596 }
597 };
598 return VmResult::Receive {
599 dest_reg: instr.a,
600 timeout: None,
601 match_tag: Some(tag),
602 match_request_id: None,
603 };
604 }
605 Opcode::FreshRequestId => {
606 let id = self.fresh_request_id();
607 trap!(self.set_reg(instr.a, Value::Int(id as i64)));
608 }
609 Opcode::ReceiveMatchCorr => {
610 let tag_v = trap!(self.get_reg(instr.b));
611 let tag = match tag_from_value(&tag_v) {
612 Ok(t) => t,
613 Err(f) => return VmResult::Trap(f),
614 };
615 let id_v = trap!(self.get_reg(instr.c));
616 let rid = match request_id_from_value(&id_v) {
617 Ok(id) => id,
618 Err(f) => return VmResult::Trap(f),
619 };
620 return VmResult::Receive {
621 dest_reg: instr.a,
622 timeout: None,
623 match_tag: Some(tag),
624 match_request_id: Some(rid),
625 };
626 }
627 Opcode::ReceiveMatchCorrImm => {
628 let tag = match u16::try_from(instr.imm) {
629 Ok(t) if instr.imm >= 0 => t,
630 _ => {
631 return VmResult::Trap(Fault::TypeMismatch {
632 expected: "tag u16",
633 got: "imm-out-of-range",
634 })
635 }
636 };
637 let id_v = trap!(self.get_reg(instr.b));
638 let rid = match request_id_from_value(&id_v) {
639 Ok(id) => id,
640 Err(f) => return VmResult::Trap(f),
641 };
642 return VmResult::Receive {
643 dest_reg: instr.a,
644 timeout: None,
645 match_tag: Some(tag),
646 match_request_id: Some(rid),
647 };
648 }
649 Opcode::Ask => {
650 let target = trap!(self.get_reg(instr.b));
651 let request = trap!(self.get_reg(instr.c));
652 let cap = match target.as_cap() {
653 Some(c) => c,
654 None => {
655 return VmResult::Trap(Fault::TypeMismatch {
656 expected: "cap",
657 got: target.type_name(),
658 })
659 }
660 };
661 if request.as_message().is_none() {
662 return VmResult::Trap(Fault::TypeMismatch {
663 expected: "message",
664 got: request.type_name(),
665 });
666 }
667 return VmResult::Ask {
668 dest_reg: instr.a,
669 target_cap: cap,
670 request,
671 timeout: None,
672 };
673 }
674 Opcode::AskTimeout => {
675 let target = trap!(self.get_reg(instr.b));
676 let request = trap!(self.get_reg(instr.c));
677 let millis_reg = match u8::try_from(instr.imm) {
678 Ok(r) => r,
679 Err(_) => {
680 return VmResult::Trap(Fault::TypeMismatch {
681 expected: "millis register",
682 got: "imm-out-of-range",
683 })
684 }
685 };
686 let millis = trap!(self.get_reg(millis_reg));
687 let ms = match millis.as_int() {
688 Some(n) if n >= 0 => n as u64,
689 _ => 0,
690 };
691 let cap = match target.as_cap() {
692 Some(c) => c,
693 None => {
694 return VmResult::Trap(Fault::TypeMismatch {
695 expected: "cap",
696 got: target.type_name(),
697 })
698 }
699 };
700 if request.as_message().is_none() {
701 return VmResult::Trap(Fault::TypeMismatch {
702 expected: "message",
703 got: request.type_name(),
704 });
705 }
706 return VmResult::Ask {
707 dest_reg: instr.a,
708 target_cap: cap,
709 request,
710 timeout: Some(Duration::from_millis(ms)),
711 };
712 }
713 Opcode::Monitor => {
714 let target = trap!(self.get_reg(instr.b));
715 let cap = match target.as_cap() {
716 Some(c) => c,
717 None => {
718 return VmResult::Trap(Fault::TypeMismatch {
719 expected: "cap",
720 got: target.type_name(),
721 })
722 }
723 };
724 return VmResult::Monitor {
725 dest_reg: instr.a,
726 target_cap: cap,
727 };
728 }
729 Opcode::Demonitor => {
730 return VmResult::Demonitor {
731 monitor_reg: instr.a,
732 };
733 }
734 Opcode::Link => {
735 let target = trap!(self.get_reg(instr.b));
736 let cap = match target.as_cap() {
737 Some(c) => c,
738 None => {
739 return VmResult::Trap(Fault::TypeMismatch {
740 expected: "cap",
741 got: target.type_name(),
742 })
743 }
744 };
745 return VmResult::Link {
746 dest_reg: instr.a,
747 target_cap: cap,
748 };
749 }
750 Opcode::Unlink => {
751 return VmResult::Unlink {
752 link_reg: instr.a,
753 };
754 }
755 Opcode::RegisterName => {
756 let name = trap!(self.get_reg(instr.a));
757 match name {
758 Value::Str(s) => {
759 return VmResult::RegisterName { name: s };
760 }
761 other => {
762 return VmResult::Trap(Fault::TypeMismatch {
763 expected: "str",
764 got: other.type_name(),
765 })
766 }
767 }
768 }
769 Opcode::Whereis => {
770 let name = trap!(self.get_reg(instr.b));
771 match name {
772 Value::Str(s) => {
773 return VmResult::Whereis {
774 dest_reg: instr.a,
775 name: s,
776 };
777 }
778 other => {
779 return VmResult::Trap(Fault::TypeMismatch {
780 expected: "str",
781 got: other.type_name(),
782 })
783 }
784 }
785 }
786 Opcode::Delegate => {
787 let src = trap!(self.get_reg(instr.b));
788 let src_cap = match src.as_cap() {
789 Some(c) => c,
790 None => {
791 return VmResult::Trap(Fault::TypeMismatch {
792 expected: "cap",
793 got: src.type_name(),
794 })
795 }
796 };
797 let want_native_cap = if instr.c == 255 {
798 None
799 } else {
800 let v = trap!(self.get_reg(instr.c));
801 match v.as_cap() {
802 Some(c) => Some(c),
803 None => {
804 return VmResult::Trap(Fault::TypeMismatch {
805 expected: "cap",
806 got: v.type_name(),
807 })
808 }
809 }
810 };
811 return VmResult::Delegate {
812 dest_reg: instr.a,
813 src_cap,
814 want_rights: crate::bytecode::CapRights::from_bits(instr.imm as u32),
815 want_native_cap,
816 };
817 }
818 Opcode::Trap => return VmResult::Trap(Fault::Explicit(instr.imm)),
819 }
820 }
821 VmResult::Yield
822 }
823
824 fn pop_frame(&mut self, value: Value) -> Result<Option<VmResult>, Fault> {
828 let finished = self
829 .frames
830 .pop()
831 .ok_or(Fault::Invariant("pop_frame on empty stack"))?;
832 match finished.dest_reg {
833 Some(dest) => {
834 if self.set_reg(dest, value).is_err() {
839 let frame_size = match self.frames.last() {
840 Some(f) => f.registers.len() as u8,
841 None => 0,
842 };
843 return Ok(Some(VmResult::Trap(Fault::RegisterOutOfRange {
844 reg: dest,
845 frame_size,
846 })));
847 }
848 Ok(None)
849 }
850 None => Ok(Some(VmResult::Complete(value))),
851 }
852 }
853}
854
855#[inline]
865fn reg_at(base: u8, offset: u16) -> Result<u8, Fault> {
866 match u8::try_from(u32::from(base) + u32::from(offset)) {
867 Ok(reg) => Ok(reg),
868 Err(_) => Err(Fault::RegisterIndexOverflow {
869 base,
870 offset: match u8::try_from(offset) {
871 Ok(o) => o,
872 Err(_) => u8::MAX,
873 },
874 }),
875 }
876}
877
878fn charge_heap_value(quota: &FlowQuota, value: &Value) -> Result<(), Fault> {
881 let bytes = match value {
882 Value::Str(s) => s.len(),
883 Value::Bytes(b) => b.len(),
884 _ => return Ok(()),
885 };
886 if bytes == 0 {
887 return Ok(());
888 }
889 quota
890 .alloc(bytes)
891 .map_err(|e| Fault::QuotaExceeded(e.to_string()))
892}
893
894fn as_f64(v: &Value) -> Result<f64, Fault> {
895 match v {
896 Value::Int(i) => Ok(*i as f64),
897 Value::Float(f) => Ok(*f),
898 other => Err(Fault::TypeMismatch { expected: "int or float", got: other.type_name() }),
899 }
900}
901
902#[inline]
904fn tag_from_value(v: &Value) -> Result<u16, Fault> {
905 match v.as_int() {
906 Some(i) if (0..=i64::from(u16::MAX)).contains(&i) => Ok(i as u16),
907 Some(_) => Err(Fault::TypeMismatch {
908 expected: "tag u16",
909 got: "int-out-of-range",
910 }),
911 None => Err(Fault::TypeMismatch {
912 expected: "int",
913 got: v.type_name(),
914 }),
915 }
916}
917
918#[inline]
920fn request_id_from_value(v: &Value) -> Result<u64, Fault> {
921 match v.as_int() {
922 Some(i) if i >= 0 => Ok(i as u64),
923 Some(_) => Err(Fault::TypeMismatch {
924 expected: "request_id u64",
925 got: "negative-int",
926 }),
927 None => Err(Fault::TypeMismatch {
928 expected: "int",
929 got: v.type_name(),
930 }),
931 }
932}
933
934#[cfg(test)]
935mod tests {
936 use super::*;
937 use crate::bytecode::builder::ChunkBuilder;
938
939 type TestResult = Result<(), Box<dyn std::error::Error>>;
940
941 #[test]
946 fn spawn_from_the_last_register_traps_instead_of_wrapping() -> TestResult {
947 let mut b = ChunkBuilder::new("t");
948 b.begin_function("main", 0, 2);
949 b.emit_spawn(255, 0, 1);
950 b.emit_return(0);
951 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), 0, &[])?;
952 match vm.run(10) {
953 VmResult::Trap(Fault::RegisterIndexOverflow {
954 base: 255,
955 offset: 1,
956 }) => Ok(()),
957 other => Err(format!("expected RegisterIndexOverflow, got {other:?}").into()),
958 }
959 }
960
961 #[test]
965 fn entering_a_function_with_too_few_registers_faults() -> TestResult {
966 let mut b = ChunkBuilder::new("t");
967 b.begin_function("main", 3, 1);
968 b.emit_return(0);
969 let args = [Value::Int(1), Value::Int(2), Value::Int(3)];
970 match Vm::new(Arc::new(b.finish()), NativeTable::empty(), 0, &args) {
971 Err(Fault::RegisterOutOfRange {
972 reg: 1,
973 frame_size: 1,
974 }) => Ok(()),
975 Err(e) => Err(format!("unexpected fault: {e}").into()),
976 Ok(_) => Err("three arguments cannot be loaded into one register".into()),
977 }
978 }
979
980 #[test]
981 fn calling_a_function_with_too_few_registers_traps() -> TestResult {
982 let mut b = ChunkBuilder::new("t");
983 let callee = b.begin_function("callee", 3, 1);
984 b.emit_return(0);
985 let main = b.begin_function("main", 0, 4);
986 b.emit_load_imm(0, 7);
987 b.emit_load_imm(1, 8);
988 b.emit_load_imm(2, 9);
989 b.emit_call(0, callee, 3);
990 b.emit_return(0);
991 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), main, &[])?;
992 match vm.run(50) {
993 VmResult::Trap(Fault::RegisterOutOfRange {
994 reg: 1,
995 frame_size: 1,
996 }) => Ok(()),
997 other => Err(format!("expected RegisterOutOfRange, got {other:?}").into()),
998 }
999 }
1000
1001 #[test]
1004 fn gathering_up_to_the_last_register_still_works() -> TestResult {
1005 let mut b = ChunkBuilder::new("t");
1006 let callee = b.begin_function("callee", 1, 1);
1007 b.emit_return(0);
1008 let main = b.begin_function("main", 0, 255);
1009 b.emit_load_imm(254, 5);
1010 b.emit_call(254, callee, 1);
1012 b.emit_return(254);
1013 let mut vm = Vm::new(Arc::new(b.finish()), NativeTable::empty(), main, &[])?;
1014 match vm.run(100) {
1015 VmResult::Complete(_) => Ok(()),
1016 other => Err(format!("expected completion, got {other:?}").into()),
1017 }
1018 }
1019}