Skip to main content

myriad/interpreter/
mod.rs

1use alloc::{string::{String, ToString}, vec::Vec};
2use self::register::{bool_u64, validate_module_register_budget};
3use super::{VirtualMachine, Value};
4use super::debug::DebugEvent;
5use polka::{BytecodeChunk, Chunk, OpCode, Register, Module, FRAME_REGS, HANDLE_NONE};
6use crate::frame::Frame;
7use crate::memory::mask_bit;
8
9pub mod frame;
10pub mod effect;
11pub mod register;
12
13pub(self) const MAX_REGISTERS: usize = 1 << 16;
14pub(self) const MAX_RECURSION_DEPTH: usize = 2048;
15// Slack for materializing param Moves before Call opcode (see stage_call_args).
16pub(self) const STAGE_SLACK: usize = 32;
17
18pub const MAX_RAM: usize = 64 * 1024 * 1024;
19
20impl VirtualMachine {
21    pub fn run(&mut self, chunk: &Chunk) -> Result<Value, String> {
22        let module = Module { functions: vec![chunk.clone()], entry: 0, flags: 0, exports: vec![] };
23        self.run_module(&module)
24    }
25
26    pub fn run_module(&mut self, module: &Module) -> Result<Value, String> {
27        let r = self.run_module_inner(module);
28        r.map_err(|e| {
29            let line = match module.functions.get(self.current_func) {
30                Some(polka::Chunk::Bytecode(bc)) => bc.lines.get(self.failing_pc).copied().unwrap_or(0),
31                _ => 0,
32            };
33            let at = if line > 0 { format!(" @{}", line) } else { String::new() };
34            format!(
35                "[{}:{}{}] {}",
36                super::debug::render_fn_label(self.current_func, &self.fn_names),
37                self.failing_pc, at, e,
38            )
39        })
40    }
41
42    pub fn call_export(
43        &mut self,
44        module: &Module,
45        name: &str,
46        args: &[Value],
47    ) -> Result<Value, String> {
48        let export = module.exports.iter().find(|e| e.name == name)
49            .ok_or_else(|| format!("no export named '{}'", name))?;
50        let fn_id = export.fn_id as usize;
51        if fn_id >= module.functions.len() {
52            return Err(format!("export '{}' fn_id {} out of range", name, fn_id));
53        }
54        let param_count = module.functions[fn_id].param_count();
55        if args.len() != param_count {
56            return Err(format!(
57                "export '{}' expects {} arg(s), got {}",
58                name, param_count, args.len()
59            ));
60        }
61        let r = self.run_export_inner(module, fn_id, args);
62        r.map_err(|e| format!(
63            "[{}:{}] {}",
64            super::debug::render_fn_label(self.current_func, &self.fn_names),
65            self.failing_pc, e,
66        ))
67    }
68
69    fn run_export_inner(
70        &mut self,
71        module: &Module,
72        fn_id: usize,
73        args: &[Value],
74    ) -> Result<Value, String> {
75        if self.exit_code.is_some() {
76            return Ok(Value::from_int(self.exit_code.unwrap()));
77        }
78        validate_module_register_budget(module)?;
79        self.int32_safe = (module.flags & polka::CART_FLAG_INT32_SAFE) != 0;
80        if self.resolved_constants.is_empty() {
81            self.frames.clear();
82            self.handlers.clear();
83            self.region_table.clear();
84            self.heap.clear();
85            self.string_const_handles.clear();
86            self.resolve_constants(module)?;
87            // First entry into this module: build statics once. They persist
88            // across later call_export invocations (heap is not cleared below).
89            self.module_table_raw = HANDLE_NONE;
90            self.module_table_is_handle = false;
91            self.run_module_init(module)?;
92        } else {
93            self.frames.clear();
94            self.handlers.clear();
95        }
96        self.pc = 0;
97        self.base_reg = 0;
98        self.current_func = fn_id;
99        self.halted = false;
100        let needed = FRAME_REGS + STAGE_SLACK;
101        self.ensure_registers(needed);
102        for (i, v) in args.iter().enumerate() {
103            self.write_abs_raw(i, v.raw());
104            self.set_reg_mask_bit(i, false);
105        }
106        self.enter_run_loop(module)
107    }
108
109    pub fn reset(&mut self) {
110        self.frames.clear();
111        self.handlers.clear();
112        self.region_table.clear();
113        self.heap.clear();
114        self.string_const_handles.clear();
115        self.resolved_constants.clear();
116        self.resolved_const_mask.clear();
117        self.resolved_natives.clear();
118        self.resolved_aot.clear();
119        self.halted = false;
120        self.exit_code = None;
121        self.pc = 0;
122        self.base_reg = 0;
123        self.module_table_raw = HANDLE_NONE;
124        self.module_table_is_handle = false;
125    }
126
127    fn run_module_inner(&mut self, module: &Module) -> Result<Value, String> {
128        validate_module_register_budget(module)?;
129        self.int32_safe = (module.flags & polka::CART_FLAG_INT32_SAFE) != 0;
130        self.frames.clear();
131        self.handlers.clear();
132        self.region_table.clear();
133        self.heap.clear();
134        self.string_const_handles.clear();
135        self.resolve_constants(module)?;
136        self.module_table_raw = HANDLE_NONE;
137        self.module_table_is_handle = false;
138        self.run_module_init(module)?;
139        self.pc = 0;
140        self.base_reg = 0;
141        self.current_func = module.entry;
142        self.halted = false;
143        self.exit_code = None;
144        let needed = FRAME_REGS + STAGE_SLACK;
145        self.ensure_registers(needed);
146
147        self.enter_run_loop(module)
148    }
149
150    // Run the synthetic `__module_init` (if the module has statics) exactly
151    // once, before the entry/export. It builds the module table and stores it
152    // via the MODULE device; its result is discarded.
153    fn run_module_init(&mut self, module: &Module) -> Result<(), String> {
154        let Some(fn_id) = module.exports.iter()
155            .find(|e| e.name == "__module_init")
156            .map(|e| e.fn_id as usize)
157        else { return Ok(()); };
158        self.pc = 0;
159        self.base_reg = 0;
160        self.current_func = fn_id;
161        self.halted = false;
162        self.exit_code = None;
163        let needed = FRAME_REGS + STAGE_SLACK;
164        self.ensure_registers(needed);
165        self.enter_run_loop(module)?;
166        Ok(())
167    }
168
169    fn enter_run_loop(&mut self, module: &Module) -> Result<Value, String> {
170        match (self.debug_sink.is_some(), self.profile) {
171            (false, false) => self.run_loop::<false, false>(module),
172            (true,  false) => self.run_loop::<true,  false>(module),
173            (false, true)  => self.run_loop::<false, true>(module),
174            (true,  true)  => self.run_loop::<true,  true>(module),
175        }
176    }
177
178    fn run_loop<const TRACE: bool, const PROF: bool>(&mut self, module: &Module) -> Result<Value, String> {
179        'outer: loop {
180            if self.yielded {
181                return Ok(Value::from_int(0));
182            }
183            if self.halted {
184                if let Some(code) = self.exit_code {
185                    self.last_result_is_handle = false;
186                    return Ok(Value::from_int(code));
187                }
188                let v = self.read_abs_raw(self.base_reg);
189                self.last_result_is_handle = self.reg_mask_bit(self.base_reg);
190                return Ok(Value::from_raw(v));
191            }
192            debug_assert!(self.current_func < module.functions.len());
193
194            let bc = match unsafe { module.functions.get_unchecked(self.current_func) } {
195                Chunk::Bytecode(b) => b,
196                Chunk::Native(_) => return Err(format!(
197                    "entry fn {} is native; cannot start execution there", self.current_func
198                )),
199            };
200            let entry_func = self.current_func;
201            loop {
202                if self.pc >= bc.code.len() {
203                    if let Some(frame) = self.frames.pop() {
204                        let return_raw = self.read_abs_raw(self.base_reg);
205                        let return_is_handle = self.reg_mask_bit(self.base_reg);
206                        self.pc = frame.ip;
207                        self.base_reg = frame.base_reg;
208                        self.current_func = frame.func_id;
209                        self.write_abs(frame.dest_reg, return_raw, return_is_handle);
210                        continue 'outer;
211                    } else {
212                        let v = self.read_abs_raw(self.base_reg);
213                        self.last_result_is_handle = self.reg_mask_bit(self.base_reg);
214                        return Ok(Value::from_raw(v));
215                    }
216                }
217                let opcode_pc = self.pc;
218                let opcode = unsafe { bc.code.get_unchecked(opcode_pc) };
219                if TRACE {
220                    let pass = self.trace_filter.as_ref()
221                        .map_or(true, |f| f.get(self.current_func).copied().unwrap_or(false));
222                    // take() the sink so the event may borrow self.registers.
223                    if pass { if let Some(mut sink) = self.debug_sink.take() {
224                        let base = self.base_reg;
225                        let rc = bc.reg_count.min(128);
226                        let end = (base + rc).min(self.registers.len());
227                        let mut handle_mask: u128 = 0;
228                        for i in 0..end.saturating_sub(base) {
229                            if self.reg_mask_bit(base + i) { handle_mask |= 1u128 << i; }
230                        }
231                        let event = DebugEvent::Trace {
232                            func: self.current_func, pc: opcode_pc, op: opcode,
233                            base_reg: base, window: &self.registers[base..end], handle_mask,
234                            line: bc.lines.get(opcode_pc).copied().unwrap_or(0),
235                            file: &bc.src_file,
236                        };
237                        sink(&event, &self.fn_names);
238                        self.debug_sink = Some(sink);
239                    } }
240                }
241                if PROF {
242                    let name = Self::op_name(opcode);
243                    *self.prof_ops.entry(name).or_insert(0) += 1;
244                    *self.prof_fns.entry(self.current_func).or_insert(0) += 1;
245                    *self.prof_fn_ops.entry(self.current_func).or_default().entry(name).or_insert(0) += 1;
246                }
247                self.pc = opcode_pc + 1;
248                self.steps = self.steps.wrapping_add(1);
249                if self.steps > self.step_cap {
250                    self.failing_pc = opcode_pc;
251                    return Err(format!("step cap exceeded ({} ops)", self.step_cap));
252                }
253                if self.heap_check {
254                    self.heap.trace_pc = opcode_pc;
255                }
256                if let Err(e) = self.exec(module, bc, opcode) {
257                    self.failing_pc = opcode_pc;
258                    return Err(e);
259                }
260                if self.heap_check {
261                    if let Err(e) = self.check_handle_tags(&format!("after {:?}", opcode)) {
262                        self.failing_pc = opcode_pc;
263                        return Err(e);
264                    }
265                }
266                if self.halted || self.yielded || self.current_func != entry_func {
267                    continue 'outer;
268                }
269            }
270        }
271    }
272
273    pub fn run_to_yield(&mut self, module: &Module) -> Result<(), String> {
274        self.yielded = false;
275        validate_module_register_budget(module)?;
276        self.int32_safe = (module.flags & polka::CART_FLAG_INT32_SAFE) != 0;
277        if self.resolved_constants.is_empty() {
278            self.frames.clear();
279            self.handlers.clear();
280            self.region_table.clear();
281            self.heap.clear();
282            self.string_const_handles.clear();
283            self.resolve_constants(module)?;
284            self.module_table_raw = polka::HANDLE_NONE;
285            self.module_table_is_handle = false;
286            self.run_module_init(module)?;
287        }
288        self.pc = 0;
289        self.base_reg = 0;
290        self.current_func = module.entry as usize;
291        self.halted = false;
292        let needed = polka::FRAME_REGS + STAGE_SLACK;
293        self.ensure_registers(needed);
294        self.enter_run_loop(module)?;
295        if !self.yielded {
296            return Err("run_to_yield: main returned without calling __frame_present".into());
297        }
298        Ok(())
299    }
300
301    pub fn resume(&mut self, module: &Module, input: Value) -> Result<bool, String> {
302        if !self.yielded {
303            return Err("resume: VM is not suspended".into());
304        }
305        self.yielded = false;
306        self.write_abs_raw(self.yield_dest_abs, input.raw());
307        self.set_reg_mask_bit(self.yield_dest_abs, false);
308        self.enter_run_loop(module)?;
309        Ok(self.yielded)
310    }
311
312    #[inline(always)]
313    fn exec(&mut self, module: &Module, bc: &BytecodeChunk, op: &OpCode) -> Result<(), String> {
314        match op {
315            OpCode::Add(d, a, b)  => self.bin_i64(*d, *a, *b, |x, y| x.wrapping_add(y)),
316            OpCode::Sub(d, a, b)  => self.bin_i64(*d, *a, *b, |x, y| x.wrapping_sub(y)),
317            OpCode::Mul(d, a, b)  => self.bin_i64(*d, *a, *b, |x, y| x.wrapping_mul(y)),
318            OpCode::Div(d, a, b)  => self.bin_i64_checked(*d, *a, *b, "div by zero", |x, y| x.checked_div(y)),
319            OpCode::Mod(d, a, b)  => self.bin_i64_checked(*d, *a, *b, "mod by zero", |x, y| x.checked_rem(y)),
320            OpCode::Neg(d, a)     => {
321                let v = self.read_i64(*a)?;
322                self.write(*d, v.wrapping_neg() as u64, false)
323            }
324
325            OpCode::FAdd(d, a, b) => self.bin_f64(*d, *a, *b, |x, y| x + y),
326            OpCode::FSub(d, a, b) => self.bin_f64(*d, *a, *b, |x, y| x - y),
327            OpCode::FMul(d, a, b) => self.bin_f64(*d, *a, *b, |x, y| x * y),
328            OpCode::FDiv(d, a, b) => self.bin_f64(*d, *a, *b, |x, y| x / y),
329            OpCode::FNeg(d, a)    => {
330                let x = self.read_f64(*a)?;
331                let bits = self.narrow_float_bits(-x);
332                self.write(*d, bits, false)
333            }
334            OpCode::FLt(d, a, b)  => {
335                let x = self.read_f64(*a)?;
336                let y = self.read_f64(*b)?;
337                let r = if x.is_nan() || y.is_nan() { false } else { x < y };
338                self.write(*d, bool_u64(r), false)
339            }
340            OpCode::FEq(d, a, b)  => {
341                let x = self.read_f64(*a)?;
342                let y = self.read_f64(*b)?;
343                let r = if x.is_nan() || y.is_nan() { false } else { x == y };
344                self.write(*d, bool_u64(r), false)
345            }
346
347            OpCode::Eq(d, a, b)  => self.bin_eq(*d, *a, *b, false),
348            OpCode::Neq(d, a, b) => self.bin_eq(*d, *a, *b, true),
349            OpCode::Lt(d, a, b)  => self.bin_i64_cmp(*d, *a, *b, |x, y| x < y),
350            OpCode::Gt(d, a, b)  => self.bin_i64_cmp(*d, *a, *b, |x, y| x > y),
351            OpCode::Lte(d, a, b) => self.bin_i64_cmp(*d, *a, *b, |x, y| x <= y),
352            OpCode::Gte(d, a, b) => self.bin_i64_cmp(*d, *a, *b, |x, y| x >= y),
353
354            OpCode::And(d, a, b) => self.bin_i64(*d, *a, *b, |x, y| x & y),
355            OpCode::Or(d, a, b)  => self.bin_i64(*d, *a, *b, |x, y| x | y),
356            OpCode::Xor(d, a, b) => self.bin_i64(*d, *a, *b, |x, y| x ^ y),
357            OpCode::Shl(d, a, b) => self.bin_i64(*d, *a, *b, |x, y| x.wrapping_shl((y as u32) & 63)),
358            OpCode::Shr(d, a, b) => self.bin_i64(*d, *a, *b, |x, y| x.wrapping_shr((y as u32) & 63)),
359
360            OpCode::Jmp(off) => self.branch(bc, *off),
361            OpCode::Jz(r, off) => {
362                let v = self.read_raw(*r)?;
363                if v == 0 { self.branch(bc, *off) } else { Ok(()) }
364            }
365            OpCode::Jnz(r, off) => {
366                let v = self.read_raw(*r)?;
367                if v != 0 { self.branch(bc, *off) } else { Ok(()) }
368            }
369            OpCode::Call(dest, fn_id) => self.do_call(module, bc, *dest, *fn_id as usize),
370            OpCode::CallReg(dest, fn_id_reg) => {
371                let fn_id = self.read_i64(*fn_id_reg)?;
372                if !(0..=0xFFFF).contains(&fn_id) {
373                    return Err(format!("call_reg: fn_id {} out of u16 range", fn_id));
374                }
375                self.do_call(module, bc, *dest, fn_id as usize)
376            }
377            OpCode::Ret(reg) => self.do_ret(module, *reg),
378
379            OpCode::PushConst(reg, pool_idx) => self.exec_push_const(*reg, *pool_idx),
380            OpCode::Copy(d, s) => {
381                let (v, is_handle) = self.read(*s)?;
382                if is_handle { self.rc_inc_handle(v)?; }
383                self.write(*d, v, is_handle)
384            }
385            OpCode::Move(d, s) => {
386                let (v, is_handle) = self.take(*s)?;
387                self.write(*d, v, is_handle)
388            }
389
390            OpCode::Ld(d, b, off)      => self.exec_ld(*d, *b, *off as i64),
391            OpCode::St(src, b, off)    => self.exec_st(*src, *b, *off as i64),
392            OpCode::LdIdx(d, b, i)     => { let off = self.read_i64(*i)?; if off < 0 { return Err(format!("ldidx: negative index {}", off)); } self.exec_ld(*d, *b, off) }
393            OpCode::StIdx(src, b, i)   => { let off = self.read_i64(*i)?; if off < 0 { return Err(format!("stidx: negative index {}", off)); } self.exec_st(*src, *b, off) }
394            OpCode::AddImm(d, s, imm) => {
395                let x = self.read_i64(*s)?;
396                self.write(*d, x.wrapping_add(*imm as i64) as u64, false)
397            }
398            OpCode::SubImm(d, s, imm) => {
399                let x = self.read_i64(*s)?;
400                self.write(*d, x.wrapping_sub(*imm as i64) as u64, false)
401            }
402
403            OpCode::Alloc(d, size) => {
404                let (slot, generation) = self.checked_heap_alloc(*size as usize)?;
405                self.region_record_alloc(slot, generation);
406                let handle = Value::from_handle(slot, generation).raw();
407                self.write(*d, handle, true)
408            }
409            OpCode::Drop(reg) => {
410                let abs = self.abs(*reg);
411                let (v, is_handle) = self.take_abs(abs);
412                if is_handle { self.rc_dec_handle(v)?; }
413                Ok(())
414            }
415
416            OpCode::Dei(d, port_reg) => self.do_dei(*d, *port_reg),
417            OpCode::Deo(src, port_reg) => self.do_deo(module, *src, *port_reg),
418            OpCode::Handle(table_reg, effect_id) => self.exec_handler_push(*table_reg, *effect_id),
419            OpCode::Resume(dest_reg, val_reg) => self.do_resume(module, *dest_reg, *val_reg),
420            OpCode::Raise(dest, key_reg, args_base) => self.do_raise(module, bc, *dest, *key_reg, *args_base),
421        }
422    }
423
424    #[inline(always)]
425    fn exec_push_const(&mut self, reg: Register, pool_idx: u16) -> Result<(), String> {
426        let idx = pool_idx as usize;
427        let consts = &self.resolved_constants[self.current_func];
428        let mask  = &self.resolved_const_mask[self.current_func];
429        if idx >= consts.len() {
430            return Err("Constant index out of bounds".to_string());
431        }
432        let raw = consts[idx];
433        let is_handle = mask_bit(mask, idx);
434        if is_handle { self.rc_inc_handle(raw)?; }
435        self.write(reg, raw, is_handle)
436    }
437
438    #[inline(always)]
439    fn exec_ld(&mut self, d: Register, b: Register, off: i64) -> Result<(), String> {
440        let (slot, gen_) = self.read_handle(b)?;
441        let (raw, is_handle) = self.heap.ld(slot, gen_, off as usize)?;
442        if is_handle { self.rc_inc_handle(raw)?; }
443        self.trace_static_access("Ld", slot, off, raw, is_handle);
444        self.write(d, raw, is_handle)
445    }
446
447    #[inline(always)]
448    fn exec_st(&mut self, src: Register, b: Register, off: i64) -> Result<(), String> {
449        let (slot, gen_) = self.read_handle(b)?;
450        let (raw, is_handle) = self.take(src)?;
451        self.trace_static_access("St", slot, off, raw, is_handle);
452        let (old_raw, old_is_handle) = self.heap.st(slot, gen_, off as usize, raw, is_handle)?;
453        if old_is_handle { self.rc_dec_handle(old_raw)?; }
454        Ok(())
455    }
456
457    fn trace_static_access(&self, op: &str, slot: u32, off: i64, raw: u64, is_handle: bool) {
458        let traced = match self.trace_static_filter.as_deref() { Some(s) => s, None => return };
459        let idx = off as usize;
460        let name = self.static_names.get(idx).map(|s| s.as_str()).unwrap_or("");
461        if name.is_empty() { return; }
462        if traced == "*" || name.contains(traced) {
463            let val = if is_handle {
464                format!("handle({:#x})", raw)
465            } else {
466                format!("int({}) / float({:.6})", raw as i64, f64::from_bits(raw))
467            };
468            if let Some(f) = self.trace_out {
469                f(&format!("[TRACE_STATIC] {} slot={} off={} name={} val={}", op, slot, off, name, val));
470            }
471        }
472    }
473
474    #[inline(always)]
475    fn exec_handler_push(&mut self, table_reg: Register, effect_id: u16) -> Result<(), String> {
476        let (table_raw, table_is_handle) = self.read_at(table_reg);
477        let (table_slot, table_gen) = if table_is_handle && table_raw != HANDLE_NONE {
478            let (s, g) = Self::decode_handle(table_raw);
479            (Some(s), g)
480        } else { (None, 0) };
481        self.handlers.push(super::HandlerFrame {
482            effect_id,
483            dispatch_table_slot: table_slot,
484            dispatch_table_gen: table_gen,
485            cell_slot: 0,
486            cell_gen: 0,
487            cells_allocated: Vec::new(),
488            body_frame_index: None,
489            pending_return_arm_fn: None,
490            pending_return_arm_env: HANDLE_NONE,
491            pending_return_arm_env_is_handle: false,
492        });
493        self.trace_frame_event("HANDLER push", format_args!("effect={:#04x}", effect_id));
494        Ok(())
495    }
496}