1use crate::op::*;
4use crate::program::*;
5use crate::value::Value;
6use indexmap::IndexMap;
7
8#[derive(Debug, Clone, thiserror::Error)]
9pub enum VmError {
10 #[error("runtime panic: {0}")]
11 Panic(String),
12 #[error("type mismatch at runtime: {0}")]
13 TypeMismatch(String),
14 #[error("stack underflow")]
15 StackUnderflow,
16 #[error("unknown function id: {0}")]
17 UnknownFunction(u32),
18 #[error("effect handler error: {0}")]
19 Effect(String),
20 #[error("call stack overflow: recursion depth exceeded ({0})")]
21 CallStackOverflow(u32),
22}
23
24pub const MAX_CALL_DEPTH: u32 = 1024;
31
32pub trait EffectHandler {
35 fn dispatch(&mut self, kind: &str, op: &str, args: Vec<Value>) -> Result<Value, String>;
36}
37
38pub struct DenyAllEffects;
40impl EffectHandler for DenyAllEffects {
41 fn dispatch(&mut self, kind: &str, op: &str, _args: Vec<Value>) -> Result<Value, String> {
42 Err(format!("effects not permitted (attempted {kind}.{op})"))
43 }
44}
45
46pub trait Tracer {
49 fn enter_call(&mut self, node_id: &str, name: &str, args: &[Value]);
50 fn enter_effect(&mut self, node_id: &str, kind: &str, op: &str, args: &[Value]);
51 fn exit_ok(&mut self, value: &Value);
52 fn exit_err(&mut self, message: &str);
53 fn exit_call_tail(&mut self);
56 fn override_effect(&mut self, _node_id: &str) -> Option<Value> { None }
58}
59
60pub struct NullTracer;
62impl Tracer for NullTracer {
63 fn enter_call(&mut self, _: &str, _: &str, _: &[Value]) {}
64 fn enter_effect(&mut self, _: &str, _: &str, _: &str, _: &[Value]) {}
65 fn exit_ok(&mut self, _: &Value) {}
66 fn exit_err(&mut self, _: &str) {}
67 fn exit_call_tail(&mut self) {}
68}
69
70#[derive(Debug, Clone)]
71pub(crate) enum FrameKind {
72 Entry,
74 Call(#[allow(dead_code)] String),
77}
78
79pub struct Vm<'a> {
80 program: &'a Program,
81 handler: Box<dyn EffectHandler + 'a>,
82 pub(crate) tracer: Box<dyn Tracer + 'a>,
83 frames: Vec<Frame>,
85 stack: Vec<Value>,
86 pub step_limit: u64,
88 pub steps: u64,
89}
90
91struct Frame {
92 fn_id: u32,
93 pc: usize,
94 locals: Vec<Value>,
95 stack_base: usize,
97 trace_kind: FrameKind,
98}
99
100fn const_str(constants: &[Const], idx: u32) -> String {
101 match constants.get(idx as usize) {
102 Some(Const::NodeId(s)) | Some(Const::Str(s)) => s.clone(),
103 _ => String::new(),
104 }
105}
106
107impl<'a> Vm<'a> {
108 pub fn new(program: &'a Program) -> Self {
109 Self::with_handler(program, Box::new(DenyAllEffects))
110 }
111
112 pub fn with_handler(program: &'a Program, handler: Box<dyn EffectHandler + 'a>) -> Self {
113 Self {
114 program,
115 handler,
116 tracer: Box::new(NullTracer),
117 frames: Vec::new(),
118 stack: Vec::new(),
119 step_limit: 10_000_000,
120 steps: 0,
121 }
122 }
123
124 pub fn set_tracer(&mut self, tracer: Box<dyn Tracer + 'a>) {
125 self.tracer = tracer;
126 }
127
128 pub fn set_step_limit(&mut self, limit: u64) {
134 self.step_limit = limit;
135 }
136
137 pub fn call(&mut self, name: &str, args: Vec<Value>) -> Result<Value, VmError> {
138 let fn_id = self.program.lookup(name).ok_or_else(|| VmError::Panic(format!("no function `{name}`")))?;
139 self.invoke(fn_id, args)
140 }
141
142 pub fn invoke(&mut self, fn_id: u32, args: Vec<Value>) -> Result<Value, VmError> {
143 let f = &self.program.functions[fn_id as usize];
144 if args.len() != f.arity as usize {
145 return Err(VmError::Panic(format!("arity mismatch calling {}", f.name)));
146 }
147 let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
148 for (i, v) in args.into_iter().enumerate() { locals[i] = v; }
149 self.push_frame(Frame {
150 fn_id, pc: 0, locals, stack_base: self.stack.len(),
151 trace_kind: FrameKind::Entry,
152 })?;
153 self.run()
154 }
155
156 fn push_frame(&mut self, frame: Frame) -> Result<(), VmError> {
161 if self.frames.len() as u32 >= MAX_CALL_DEPTH {
162 return Err(VmError::CallStackOverflow(MAX_CALL_DEPTH));
163 }
164 self.frames.push(frame);
165 Ok(())
166 }
167
168 fn run(&mut self) -> Result<Value, VmError> {
169 loop {
170 if self.steps > self.step_limit {
171 return Err(VmError::Panic(format!(
172 "step limit exceeded ({} > {})",
173 self.steps, self.step_limit,
174 )));
175 }
176 self.steps += 1;
177 let frame_idx = self.frames.len() - 1;
178 let pc = self.frames[frame_idx].pc;
179 let fn_id = self.frames[frame_idx].fn_id;
180 let code = &self.program.functions[fn_id as usize].code;
181 if pc >= code.len() {
182 return Err(VmError::Panic("ran past end of code".into()));
183 }
184 let op = code[pc].clone();
185 self.frames[frame_idx].pc = pc + 1;
186
187 match op {
188 Op::PushConst(i) => {
189 let c = &self.program.constants[i as usize];
190 self.stack.push(const_to_value(c));
191 }
192 Op::Pop => { self.pop()?; }
193 Op::Dup => {
194 let v = self.peek()?.clone();
195 self.stack.push(v);
196 }
197 Op::LoadLocal(i) => {
198 let v = self.frames[frame_idx].locals[i as usize].clone();
199 self.stack.push(v);
200 }
201 Op::StoreLocal(i) => {
202 let v = self.pop()?;
203 self.frames[frame_idx].locals[i as usize] = v;
204 }
205 Op::MakeRecord { field_name_indices } => {
206 let n = field_name_indices.len();
207 let mut values: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
208 for i in (0..n).rev() {
209 values[i] = self.pop()?;
210 }
211 let mut rec = IndexMap::new();
212 for (i, val) in values.into_iter().enumerate() {
213 let name = match &self.program.constants[field_name_indices[i] as usize] {
214 Const::FieldName(s) => s.clone(),
215 _ => return Err(VmError::TypeMismatch("expected FieldName const".into())),
216 };
217 rec.insert(name, val);
218 }
219 self.stack.push(Value::Record(rec));
220 }
221 Op::MakeTuple(n) => {
222 let mut items: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
223 for i in (0..n as usize).rev() { items[i] = self.pop()?; }
224 self.stack.push(Value::Tuple(items));
225 }
226 Op::MakeList(n) => {
227 let mut items: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
228 for i in (0..n as usize).rev() { items[i] = self.pop()?; }
229 self.stack.push(Value::List(items));
230 }
231 Op::MakeVariant { name_idx, arity } => {
232 let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
233 for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
234 let name = match &self.program.constants[name_idx as usize] {
235 Const::VariantName(s) => s.clone(),
236 _ => return Err(VmError::TypeMismatch("expected VariantName const".into())),
237 };
238 self.stack.push(Value::Variant { name, args });
239 }
240 Op::GetField(i) => {
241 let name = match &self.program.constants[i as usize] {
242 Const::FieldName(s) => s.clone(),
243 _ => return Err(VmError::TypeMismatch("expected FieldName const".into())),
244 };
245 let v = self.pop()?;
246 match v {
247 Value::Record(r) => {
248 let v = r.get(&name).cloned()
249 .ok_or_else(|| VmError::TypeMismatch(format!("missing field `{name}`")))?;
250 self.stack.push(v);
251 }
252 other => return Err(VmError::TypeMismatch(format!("GetField on non-record: {other:?}"))),
253 }
254 }
255 Op::GetElem(i) => {
256 let v = self.pop()?;
257 match v {
258 Value::Tuple(items) => {
259 let v = items.into_iter().nth(i as usize)
260 .ok_or_else(|| VmError::TypeMismatch(format!("tuple index {i} out of range")))?;
261 self.stack.push(v);
262 }
263 other => return Err(VmError::TypeMismatch(format!("GetElem on non-tuple: {other:?}"))),
264 }
265 }
266 Op::TestVariant(i) => {
267 let name = match &self.program.constants[i as usize] {
268 Const::VariantName(s) => s.clone(),
269 _ => return Err(VmError::TypeMismatch("expected VariantName const".into())),
270 };
271 let v = self.pop()?;
272 match &v {
273 Value::Variant { name: vname, .. } => {
274 self.stack.push(Value::Bool(vname == &name));
275 }
276 other => return Err(VmError::TypeMismatch(format!("TestVariant on non-variant: {other:?}"))),
279 }
280 }
281 Op::GetVariant(_i) => {
282 let v = self.pop()?;
283 match v {
284 Value::Variant { args, .. } => {
285 self.stack.push(Value::Tuple(args));
286 }
287 other => return Err(VmError::TypeMismatch(format!("GetVariant on non-variant: {other:?}"))),
288 }
289 }
290 Op::GetVariantArg(i) => {
291 let v = self.pop()?;
292 match v {
293 Value::Variant { mut args, .. } => {
294 if (i as usize) >= args.len() {
295 return Err(VmError::TypeMismatch("variant arg index oob".into()));
296 }
297 self.stack.push(args.swap_remove(i as usize));
298 }
299 other => return Err(VmError::TypeMismatch(format!("GetVariantArg on non-variant: {other:?}"))),
300 }
301 }
302 Op::GetListLen => {
303 let v = self.pop()?;
304 match v {
305 Value::List(items) => self.stack.push(Value::Int(items.len() as i64)),
306 other => return Err(VmError::TypeMismatch(format!("GetListLen on non-list: {other:?}"))),
307 }
308 }
309 Op::GetListElem(i) => {
310 let v = self.pop()?;
311 match v {
312 Value::List(items) => {
313 let v = items.into_iter().nth(i as usize)
314 .ok_or_else(|| VmError::TypeMismatch("list index oob".into()))?;
315 self.stack.push(v);
316 }
317 other => return Err(VmError::TypeMismatch(format!("GetListElem on non-list: {other:?}"))),
318 }
319 }
320 Op::GetListElemDyn => {
321 let idx = match self.pop()? {
323 Value::Int(n) => n as usize,
324 other => return Err(VmError::TypeMismatch(format!("GetListElemDyn idx: {other:?}"))),
325 };
326 let v = self.pop()?;
327 match v {
328 Value::List(items) => {
329 let v = items.into_iter().nth(idx)
330 .ok_or_else(|| VmError::TypeMismatch("list index oob".into()))?;
331 self.stack.push(v);
332 }
333 other => return Err(VmError::TypeMismatch(format!("GetListElemDyn on non-list: {other:?}"))),
334 }
335 }
336 Op::ListAppend => {
337 let value = self.pop()?;
338 let list = self.pop()?;
339 match list {
340 Value::List(mut items) => {
341 items.push(value);
342 self.stack.push(Value::List(items));
343 }
344 other => return Err(VmError::TypeMismatch(format!("ListAppend on non-list: {other:?}"))),
345 }
346 }
347 Op::Jump(off) => {
348 let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
349 self.frames[frame_idx].pc = new_pc;
350 }
351 Op::JumpIf(off) => {
352 let v = self.pop()?;
353 if v.as_bool() {
354 let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
355 self.frames[frame_idx].pc = new_pc;
356 }
357 }
358 Op::JumpIfNot(off) => {
359 let v = self.pop()?;
360 if !v.as_bool() {
361 let new_pc = (self.frames[frame_idx].pc as i32 + off) as usize;
362 self.frames[frame_idx].pc = new_pc;
363 }
364 }
365 Op::MakeClosure { fn_id, capture_count } => {
366 let n = capture_count as usize;
367 let mut captures: Vec<Value> = (0..n).map(|_| Value::Unit).collect();
368 for i in (0..n).rev() { captures[i] = self.pop()?; }
369 self.stack.push(Value::Closure { fn_id, captures });
370 }
371 Op::CallClosure { arity, node_id_idx } => {
372 let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
373 for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
374 let closure = self.pop()?;
375 let (fn_id, captures) = match closure {
376 Value::Closure { fn_id, captures } => (fn_id, captures),
377 other => return Err(VmError::TypeMismatch(format!("CallClosure on non-closure: {other:?}"))),
378 };
379 let node_id = const_str(&self.program.constants, node_id_idx);
380 let callee_name = self.program.functions[fn_id as usize].name.clone();
381 let mut combined = captures;
382 combined.extend(args);
383 self.tracer.enter_call(&node_id, &callee_name, &combined);
384 let f = &self.program.functions[fn_id as usize];
385 let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
386 for (i, v) in combined.into_iter().enumerate() { locals[i] = v; }
387 self.push_frame(Frame {
388 fn_id, pc: 0, locals, stack_base: self.stack.len(),
389 trace_kind: FrameKind::Call(node_id),
390 })?;
391 }
392 Op::Call { fn_id, arity, node_id_idx } => {
393 let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
394 for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
395 let node_id = const_str(&self.program.constants, node_id_idx);
396 let callee_name = self.program.functions[fn_id as usize].name.clone();
397 self.tracer.enter_call(&node_id, &callee_name, &args);
398 let f = &self.program.functions[fn_id as usize];
399 let mut locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
400 for (i, v) in args.into_iter().enumerate() { locals[i] = v; }
401 self.push_frame(Frame {
402 fn_id, pc: 0, locals, stack_base: self.stack.len(),
403 trace_kind: FrameKind::Call(node_id),
404 })?;
405 }
406 Op::TailCall { fn_id, arity, node_id_idx } => {
407 let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
408 for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
409 let node_id = const_str(&self.program.constants, node_id_idx);
410 let callee_name = self.program.functions[fn_id as usize].name.clone();
411 self.tracer.exit_call_tail();
415 self.tracer.enter_call(&node_id, &callee_name, &args);
416 let f = &self.program.functions[fn_id as usize];
417 let frame = self.frames.last_mut().unwrap();
418 frame.fn_id = fn_id;
419 frame.pc = 0;
420 frame.locals = vec![Value::Unit; f.locals_count.max(f.arity) as usize];
421 for (i, v) in args.into_iter().enumerate() { frame.locals[i] = v; }
422 frame.trace_kind = FrameKind::Call(node_id);
423 }
424 Op::EffectCall { kind_idx, op_idx, arity, node_id_idx } => {
425 let mut args: Vec<Value> = (0..arity).map(|_| Value::Unit).collect();
426 for i in (0..arity as usize).rev() { args[i] = self.pop()?; }
427 let kind = match &self.program.constants[kind_idx as usize] {
428 Const::Str(s) => s.clone(),
429 _ => return Err(VmError::TypeMismatch("expected Str const for effect kind".into())),
430 };
431 let op_name = match &self.program.constants[op_idx as usize] {
432 Const::Str(s) => s.clone(),
433 _ => return Err(VmError::TypeMismatch("expected Str const for effect op".into())),
434 };
435 let node_id = const_str(&self.program.constants, node_id_idx);
436 self.tracer.enter_effect(&node_id, &kind, &op_name, &args);
437 let result = match self.tracer.override_effect(&node_id) {
438 Some(v) => Ok(v),
439 None => self.handler.dispatch(&kind, &op_name, args.clone()),
440 };
441 match result {
442 Ok(v) => {
443 self.tracer.exit_ok(&v);
444 self.stack.push(v);
445 }
446 Err(e) => {
447 self.tracer.exit_err(&e);
448 return Err(VmError::Effect(e));
449 }
450 }
451 }
452 Op::Return => {
453 let v = self.pop()?;
454 let frame = self.frames.pop().unwrap();
455 self.stack.truncate(frame.stack_base);
457 if matches!(frame.trace_kind, FrameKind::Call(_)) {
458 self.tracer.exit_ok(&v);
459 }
460 if self.frames.is_empty() {
461 return Ok(v);
462 }
463 self.stack.push(v);
464 }
465 Op::Panic(i) => {
466 let msg = match &self.program.constants[i as usize] {
467 Const::Str(s) => s.clone(),
468 _ => "panic".into(),
469 };
470 return Err(VmError::Panic(msg));
471 }
472 Op::IntAdd => self.bin_int(|a, b| Value::Int(a + b))?,
474 Op::IntSub => self.bin_int(|a, b| Value::Int(a - b))?,
475 Op::IntMul => self.bin_int(|a, b| Value::Int(a * b))?,
476 Op::IntDiv => self.bin_int(|a, b| Value::Int(a / b))?,
477 Op::IntMod => self.bin_int(|a, b| Value::Int(a % b))?,
478 Op::IntNeg => {
479 let a = self.pop()?.as_int();
480 self.stack.push(Value::Int(-a));
481 }
482 Op::IntEq => self.bin_int(|a, b| Value::Bool(a == b))?,
483 Op::IntLt => self.bin_int(|a, b| Value::Bool(a < b))?,
484 Op::IntLe => self.bin_int(|a, b| Value::Bool(a <= b))?,
485 Op::FloatAdd => self.bin_float(|a, b| Value::Float(a + b))?,
486 Op::FloatSub => self.bin_float(|a, b| Value::Float(a - b))?,
487 Op::FloatMul => self.bin_float(|a, b| Value::Float(a * b))?,
488 Op::FloatDiv => self.bin_float(|a, b| Value::Float(a / b))?,
489 Op::FloatNeg => {
490 let a = self.pop()?.as_float();
491 self.stack.push(Value::Float(-a));
492 }
493 Op::FloatEq => self.bin_float(|a, b| Value::Bool(a == b))?,
494 Op::FloatLt => self.bin_float(|a, b| Value::Bool(a < b))?,
495 Op::FloatLe => self.bin_float(|a, b| Value::Bool(a <= b))?,
496 Op::NumAdd => self.bin_num(|a, b| Value::Int(a + b), |a, b| Value::Float(a + b))?,
497 Op::NumSub => self.bin_num(|a, b| Value::Int(a - b), |a, b| Value::Float(a - b))?,
498 Op::NumMul => self.bin_num(|a, b| Value::Int(a * b), |a, b| Value::Float(a * b))?,
499 Op::NumDiv => self.bin_num(|a, b| Value::Int(a / b), |a, b| Value::Float(a / b))?,
500 Op::NumMod => self.bin_int(|a, b| Value::Int(a % b))?,
501 Op::NumNeg => {
502 let v = self.pop()?;
503 match v {
504 Value::Int(n) => self.stack.push(Value::Int(-n)),
505 Value::Float(f) => self.stack.push(Value::Float(-f)),
506 other => return Err(VmError::TypeMismatch(format!("NumNeg on {other:?}"))),
507 }
508 }
509 Op::NumEq => self.bin_eq()?,
510 Op::NumLt => self.bin_num(|a, b| Value::Bool(a < b), |a, b| Value::Bool(a < b))?,
511 Op::NumLe => self.bin_num(|a, b| Value::Bool(a <= b), |a, b| Value::Bool(a <= b))?,
512 Op::BoolAnd => {
513 let b = self.pop()?.as_bool();
514 let a = self.pop()?.as_bool();
515 self.stack.push(Value::Bool(a && b));
516 }
517 Op::BoolOr => {
518 let b = self.pop()?.as_bool();
519 let a = self.pop()?.as_bool();
520 self.stack.push(Value::Bool(a || b));
521 }
522 Op::BoolNot => {
523 let a = self.pop()?.as_bool();
524 self.stack.push(Value::Bool(!a));
525 }
526 Op::StrConcat => {
527 let b = self.pop()?;
528 let a = self.pop()?;
529 let s = format!("{}{}", a.as_str(), b.as_str());
530 self.stack.push(Value::Str(s));
531 }
532 Op::StrLen => {
533 let v = self.pop()?;
534 self.stack.push(Value::Int(v.as_str().len() as i64));
535 }
536 Op::StrEq => {
537 let b = self.pop()?;
538 let a = self.pop()?;
539 self.stack.push(Value::Bool(a.as_str() == b.as_str()));
540 }
541 Op::BytesLen => {
542 let v = self.pop()?;
543 match v {
544 Value::Bytes(b) => self.stack.push(Value::Int(b.len() as i64)),
545 other => return Err(VmError::TypeMismatch(format!("BytesLen on {other:?}"))),
546 }
547 }
548 Op::BytesEq => {
549 let b = self.pop()?;
550 let a = self.pop()?;
551 let eq = match (a, b) {
552 (Value::Bytes(x), Value::Bytes(y)) => x == y,
553 _ => return Err(VmError::TypeMismatch("BytesEq operands".into())),
554 };
555 self.stack.push(Value::Bool(eq));
556 }
557 }
558 }
559 }
560
561 fn pop(&mut self) -> Result<Value, VmError> {
562 self.stack.pop().ok_or(VmError::StackUnderflow)
563 }
564 fn peek(&self) -> Result<&Value, VmError> {
565 self.stack.last().ok_or(VmError::StackUnderflow)
566 }
567
568 fn bin_int(&mut self, f: impl Fn(i64, i64) -> Value) -> Result<(), VmError> {
569 let b = self.pop()?.as_int();
570 let a = self.pop()?.as_int();
571 self.stack.push(f(a, b));
572 Ok(())
573 }
574 fn bin_float(&mut self, f: impl Fn(f64, f64) -> Value) -> Result<(), VmError> {
575 let b = self.pop()?.as_float();
576 let a = self.pop()?.as_float();
577 self.stack.push(f(a, b));
578 Ok(())
579 }
580 fn bin_num(
581 &mut self,
582 i: impl Fn(i64, i64) -> Value,
583 f: impl Fn(f64, f64) -> Value,
584 ) -> Result<(), VmError> {
585 let b = self.pop()?;
586 let a = self.pop()?;
587 match (a, b) {
588 (Value::Int(x), Value::Int(y)) => { self.stack.push(i(x, y)); Ok(()) }
589 (Value::Float(x), Value::Float(y)) => { self.stack.push(f(x, y)); Ok(()) }
590 (a, b) => Err(VmError::TypeMismatch(format!("Num op: {a:?} {b:?}"))),
591 }
592 }
593 fn bin_eq(&mut self) -> Result<(), VmError> {
594 let b = self.pop()?;
595 let a = self.pop()?;
596 self.stack.push(Value::Bool(a == b));
597 Ok(())
598 }
599}
600
601fn const_to_value(c: &Const) -> Value {
602 match c {
603 Const::Int(n) => Value::Int(*n),
604 Const::Float(f) => Value::Float(*f),
605 Const::Bool(b) => Value::Bool(*b),
606 Const::Str(s) => Value::Str(s.clone()),
607 Const::Bytes(b) => Value::Bytes(b.clone()),
608 Const::Unit => Value::Unit,
609 Const::FieldName(s) | Const::VariantName(s) | Const::NodeId(s) => Value::Str(s.clone()),
610 }
611}