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;
15pub(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 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 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 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}