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) {
46 frame.registers[i] = arg.clone();
47 }
48 Ok(Vm { chunk, natives, frames: vec![frame], instructions_executed: 0 })
49 }
50
51 pub fn instructions_executed(&self) -> u64 {
52 self.instructions_executed
53 }
54
55 pub fn current_function(&self) -> u32 {
58 debug_assert!(!self.frames.is_empty(), "frames empty while running");
62 match self.frames.last() {
63 Some(frame) => frame.function,
64 None => 0,
65 }
66 }
67
68 pub fn chunk_arc(&self) -> Arc<Chunk> {
74 self.chunk.clone()
75 }
76
77 pub fn natives_arc(&self) -> Arc<NativeTable> {
81 self.natives.clone()
82 }
83
84 #[inline]
91 pub fn resume_with(&mut self, dest_reg: u8, value: Value) -> Result<(), Fault> {
92 self.set_reg(dest_reg, value)
93 }
94
95 #[inline]
98 fn current(&mut self) -> Result<&mut Frame, Fault> {
99 debug_assert!(!self.frames.is_empty(), "frames empty while running");
100 self.frames
101 .last_mut()
102 .ok_or(Fault::Invariant("empty frame stack while running"))
103 }
104
105 #[inline]
106 fn get_reg(&self, reg: u8) -> Result<Value, Fault> {
107 let frame = self
108 .frames
109 .last()
110 .ok_or(Fault::Invariant("empty frame stack while running"))?;
111 frame
112 .registers
113 .get(reg as usize)
114 .cloned()
115 .ok_or(Fault::RegisterOutOfRange {
116 reg,
117 frame_size: frame.registers.len() as u8,
118 })
119 }
120
121 #[inline]
122 fn set_reg(&mut self, reg: u8, value: Value) -> Result<(), Fault> {
123 let frame = self.current()?;
124 let len = frame.registers.len() as u8;
125 match frame.registers.get_mut(reg as usize) {
126 Some(slot) => {
127 *slot = value;
128 Ok(())
129 }
130 None => Err(Fault::RegisterOutOfRange { reg, frame_size: len }),
131 }
132 }
133
134 #[inline]
145 fn fetch(&mut self) -> Result<Option<Instruction>, Fault> {
146 let pc = self.current()?.pc;
147 let instr = self.chunk.code.get(pc).copied();
148 if instr.is_some() {
149 self.current()?.pc = pc + 1;
150 }
151 Ok(instr)
152 }
153
154 fn numeric_binop(&mut self, op: Opcode, dst: u8, lhs: u8, rhs: u8) -> Result<(), Fault> {
155 let a = self.get_reg(lhs)?;
156 let b = self.get_reg(rhs)?;
157 let result = match (op, &a, &b) {
158 (Opcode::Add, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_add(*y)),
159 (Opcode::Add, _, _) => Value::Float(as_f64(&a)? + as_f64(&b)?),
160 (Opcode::Sub, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_sub(*y)),
161 (Opcode::Sub, _, _) => Value::Float(as_f64(&a)? - as_f64(&b)?),
162 (Opcode::Mul, Value::Int(x), Value::Int(y)) => Value::Int(x.wrapping_mul(*y)),
163 (Opcode::Mul, _, _) => Value::Float(as_f64(&a)? * as_f64(&b)?),
164 (Opcode::Div, Value::Int(x), Value::Int(y)) => {
165 if *y == 0 {
166 return Err(Fault::DivideByZero);
167 }
168 Value::Int(x.wrapping_div(*y))
169 }
170 (Opcode::Div, _, _) => {
171 let denom = as_f64(&b)?;
172 Value::Float(as_f64(&a)? / denom)
173 }
174 (Opcode::Mod, Value::Int(x), Value::Int(y)) => {
175 if *y == 0 {
176 return Err(Fault::DivideByZero);
177 }
178 Value::Int(x.wrapping_rem(*y))
179 }
180 (Opcode::Eq, _, _) => Value::Bool(a == b),
181 (Opcode::Lt, Value::Int(x), Value::Int(y)) => Value::Bool(x < y),
182 (Opcode::Lt, _, _) => Value::Bool(as_f64(&a)? < as_f64(&b)?),
183 (Opcode::Le, Value::Int(x), Value::Int(y)) => Value::Bool(x <= y),
184 (Opcode::Le, _, _) => Value::Bool(as_f64(&a)? <= as_f64(&b)?),
185 _ => unreachable!("numeric_binop called with non-arithmetic opcode {op:?}"),
186 };
187 self.set_reg(dst, result)
188 }
189
190 pub fn run(&mut self, budget: u32) -> VmResult {
198 for _ in 0..budget {
199 self.instructions_executed += 1;
200 let instr = match self.fetch() {
201 Ok(Some(i)) => i,
202 Ok(None) => {
203 match self.pop_frame(Value::Unit) {
205 Ok(Some(result)) => return result,
206 Ok(None) => continue,
207 Err(fault) => return VmResult::Trap(fault),
208 }
209 }
210 Err(fault) => return VmResult::Trap(fault),
211 };
212
213 macro_rules! trap {
214 ($e:expr) => {
215 match $e {
216 Ok(v) => v,
217 Err(fault) => return VmResult::Trap(fault),
218 }
219 };
220 }
221
222 match instr.op {
223 Opcode::Halt => {
224 let v = trap!(self.get_reg(0));
225 return VmResult::Complete(v);
226 }
227 Opcode::Nop => {}
228 Opcode::LoadConst => {
229 let idx = instr.imm as u32;
230 let konst = match self.chunk.constant(idx) {
231 Some(v) => v.clone(),
232 None => {
233 return VmResult::Trap(Fault::BadConstant {
234 index: idx,
235 pool_size: self.chunk.constants.len() as u32,
236 })
237 }
238 };
239 trap!(self.set_reg(instr.a, konst));
240 }
241 Opcode::LoadImm => {
242 trap!(self.set_reg(instr.a, Value::Int(instr.imm as i64)));
243 }
244 Opcode::Move => {
245 let v = trap!(self.get_reg(instr.b));
246 trap!(self.set_reg(instr.a, v));
247 }
248 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Div | Opcode::Mod
249 | Opcode::Eq | Opcode::Lt | Opcode::Le => {
250 trap!(self.numeric_binop(instr.op, instr.a, instr.b, instr.c));
251 }
252 Opcode::Neg => {
253 let v = trap!(self.get_reg(instr.b));
254 let negated = match v {
255 Value::Int(x) => Value::Int(-x),
256 Value::Float(x) => Value::Float(-x),
257 other => {
258 return VmResult::Trap(Fault::TypeMismatch {
259 expected: "int or float",
260 got: other.type_name(),
261 })
262 }
263 };
264 trap!(self.set_reg(instr.a, negated));
265 }
266 Opcode::Jump => {
267 let frame = trap!(self.current());
268 let target = frame.pc as i64 + instr.imm as i64;
269 frame.pc = target as usize;
270 }
271 Opcode::Branch => {
272 let cond = trap!(self.get_reg(instr.a));
273 if !cond.is_truthy() {
274 let frame = trap!(self.current());
275 let target = frame.pc as i64 + instr.imm as i64;
276 frame.pc = target as usize;
277 }
278 }
279 Opcode::Call => {
280 let function = instr.imm as u32;
281 let argc = instr.b;
282 let dst = instr.a;
283 if self.frames.len() >= MAX_CALL_DEPTH {
284 return VmResult::Trap(Fault::CallStackOverflow { depth: self.frames.len() });
285 }
286 let def = match self.chunk.function(function) {
287 Some(d) => d.clone(),
288 None => {
289 return VmResult::Trap(Fault::BadFunction {
290 index: function,
291 table_size: self.chunk.functions.len() as u32,
292 })
293 }
294 };
295 let mut args = Vec::with_capacity(argc as usize);
296 for i in 0..argc {
297 args.push(trap!(self.get_reg(dst + i)));
298 }
299 let mut new_frame = Frame::new(function, def.num_registers, Some(dst));
300 new_frame.pc = def.entry as usize;
301 for (i, a) in args.into_iter().enumerate().take(def.arity as usize) {
302 new_frame.registers[i] = a;
303 }
304 self.frames.push(new_frame);
305 }
306 Opcode::CallNative => {
307 let native_index = instr.imm as u32;
308 let argc = instr.b;
309 let dst = instr.a;
310 let native_fn = match self.natives.get(native_index) {
311 Some(f) => f.clone(),
312 None => {
313 return VmResult::Trap(Fault::BadNative {
314 index: native_index,
315 table_size: self.natives.len() as u32,
316 })
317 }
318 };
319 let mut args = Vec::with_capacity(argc as usize);
320 for i in 0..argc {
321 args.push(trap!(self.get_reg(dst + i)));
322 }
323 match native_fn(&args) {
331 Ok(value) => trap!(self.set_reg(dst, value)),
332 Err(fault) => return VmResult::Trap(fault),
333 }
334 }
335 Opcode::Return => {
336 let v = trap!(self.get_reg(instr.a));
337 match self.pop_frame(v) {
338 Ok(Some(result)) => return result,
339 Ok(None) => {}
340 Err(fault) => return VmResult::Trap(fault),
341 }
342 }
343 Opcode::Spawn => {
344 let argc = instr.b;
345 let mut args = Vec::with_capacity(argc as usize);
346 for i in 0..argc {
347 args.push(trap!(self.get_reg(instr.a + 1 + i)));
352 }
353 return VmResult::Spawn { function: instr.imm as u32, args, dest_reg: instr.a };
354 }
355 Opcode::Yield => return VmResult::Yield,
356 Opcode::Sleep => {
357 let millis = trap!(self.get_reg(instr.a));
358 let ms = match millis.as_int() {
359 Some(ms) if ms >= 0 => ms as u64,
360 _ => {
361 return VmResult::Trap(Fault::TypeMismatch {
362 expected: "non-negative int",
363 got: millis.type_name(),
364 })
365 }
366 };
367 return VmResult::Sleep(Duration::from_millis(ms));
368 }
369 Opcode::Exit => {
370 let v = trap!(self.get_reg(instr.a));
371 return VmResult::Complete(v);
372 }
373 Opcode::SelfPid => {
374 return VmResult::SelfPid { dest_reg: instr.a };
375 }
376 Opcode::Send => {
377 let target = trap!(self.get_reg(instr.a));
378 let message = trap!(self.get_reg(instr.b));
379 let cap = match target.as_cap() {
380 Some(c) => c,
381 None => {
382 return VmResult::Trap(Fault::TypeMismatch {
383 expected: "cap",
384 got: target.type_name(),
385 })
386 }
387 };
388 if message.as_message().is_none() {
389 return VmResult::Trap(Fault::TypeMismatch {
390 expected: "message",
391 got: message.type_name(),
392 });
393 }
394 return VmResult::Send {
395 target_cap: cap,
396 message,
397 };
398 }
399 Opcode::Receive => {
400 return VmResult::Receive {
401 dest_reg: instr.a,
402 timeout: None,
403 match_tag: None,
404 };
405 }
406 Opcode::ReceiveTimeout => {
407 let millis = trap!(self.get_reg(instr.b));
408 let ms = millis.as_int().unwrap_or(0).max(0) as u64;
409 return VmResult::Receive {
410 dest_reg: instr.a,
411 timeout: Some(Duration::from_millis(ms)),
412 match_tag: None,
413 };
414 }
415 Opcode::ReceiveMatch => {
416 let tag_v = trap!(self.get_reg(instr.b));
417 let tag = match tag_from_value(&tag_v) {
418 Ok(t) => t,
419 Err(f) => return VmResult::Trap(f),
420 };
421 return VmResult::Receive {
422 dest_reg: instr.a,
423 timeout: None,
424 match_tag: Some(tag),
425 };
426 }
427 Opcode::ReceiveMatchImm => {
428 let tag = match u16::try_from(instr.imm) {
429 Ok(t) if instr.imm >= 0 => t,
430 _ => {
431 return VmResult::Trap(Fault::TypeMismatch {
432 expected: "tag u16",
433 got: "imm-out-of-range",
434 })
435 }
436 };
437 return VmResult::Receive {
438 dest_reg: instr.a,
439 timeout: None,
440 match_tag: Some(tag),
441 };
442 }
443 Opcode::Ask => {
444 let target = trap!(self.get_reg(instr.b));
445 let request = trap!(self.get_reg(instr.c));
446 let cap = match target.as_cap() {
447 Some(c) => c,
448 None => {
449 return VmResult::Trap(Fault::TypeMismatch {
450 expected: "cap",
451 got: target.type_name(),
452 })
453 }
454 };
455 if request.as_message().is_none() {
456 return VmResult::Trap(Fault::TypeMismatch {
457 expected: "message",
458 got: request.type_name(),
459 });
460 }
461 return VmResult::Ask {
462 dest_reg: instr.a,
463 target_cap: cap,
464 request,
465 };
466 }
467 Opcode::Trap => return VmResult::Trap(Fault::Explicit(instr.imm)),
468 }
469 }
470 VmResult::Yield
471 }
472
473 fn pop_frame(&mut self, value: Value) -> Result<Option<VmResult>, Fault> {
477 let finished = self
478 .frames
479 .pop()
480 .ok_or(Fault::Invariant("pop_frame on empty stack"))?;
481 match finished.dest_reg {
482 Some(dest) => {
483 if self.set_reg(dest, value).is_err() {
488 return Ok(Some(VmResult::Trap(Fault::RegisterOutOfRange {
489 reg: dest,
490 frame_size: self
491 .frames
492 .last()
493 .map(|f| f.registers.len() as u8)
494 .unwrap_or(0),
495 })));
496 }
497 Ok(None)
498 }
499 None => Ok(Some(VmResult::Complete(value))),
500 }
501 }
502}
503
504#[inline]
505fn as_f64(v: &Value) -> Result<f64, Fault> {
506 match v {
507 Value::Int(i) => Ok(*i as f64),
508 Value::Float(f) => Ok(*f),
509 other => Err(Fault::TypeMismatch { expected: "int or float", got: other.type_name() }),
510 }
511}
512
513#[inline]
515fn tag_from_value(v: &Value) -> Result<u16, Fault> {
516 match v.as_int() {
517 Some(i) if (0..=i64::from(u16::MAX)).contains(&i) => Ok(i as u16),
518 Some(_) => Err(Fault::TypeMismatch {
519 expected: "tag u16",
520 got: "int-out-of-range",
521 }),
522 None => Err(Fault::TypeMismatch {
523 expected: "int",
524 got: v.type_name(),
525 }),
526 }
527}