1use std::collections::{BTreeMap, HashSet};
2use std::rc::Rc;
3use std::sync::Arc;
4use std::time::Instant;
5
6use crate::chunk::{Chunk, ChunkRef, Constant};
7use crate::value::{
8 ModuleFunctionRegistry, VmAsyncBuiltinFn, VmBuiltinFn, VmEnv, VmError, VmTaskHandle, VmValue,
9};
10use crate::BuiltinId;
11
12use super::debug::DebugHook;
13use super::modules::LoadedModule;
14use super::VmBuiltinMetadata;
15
16pub(crate) struct ScopeSpan(u64);
18
19impl ScopeSpan {
20 pub(crate) fn new(kind: crate::tracing::SpanKind, name: String) -> Self {
21 Self(crate::tracing::span_start(kind, name))
22 }
23}
24
25impl Drop for ScopeSpan {
26 fn drop(&mut self) {
27 crate::tracing::span_end(self.0);
28 }
29}
30
31#[derive(Clone)]
32pub(crate) struct LocalSlot {
33 pub(crate) value: VmValue,
34 pub(crate) initialized: bool,
35 pub(crate) synced: bool,
36}
37
38pub(crate) struct CallFrame {
40 pub(crate) chunk: ChunkRef,
41 pub(crate) ip: usize,
42 pub(crate) stack_base: usize,
43 pub(crate) saved_env: VmEnv,
44 pub(crate) initial_env: Option<VmEnv>,
52 pub(crate) initial_local_slots: Option<Vec<LocalSlot>>,
53 pub(crate) saved_iterator_depth: usize,
55 pub(crate) fn_name: String,
57 pub(crate) argc: usize,
59 pub(crate) saved_source_dir: Option<std::path::PathBuf>,
62 pub(crate) module_functions: Option<ModuleFunctionRegistry>,
64 pub(crate) module_state: Option<crate::value::ModuleState>,
70 pub(crate) local_slots: Vec<LocalSlot>,
72 pub(crate) local_scope_base: usize,
74 pub(crate) local_scope_depth: usize,
76}
77
78pub(crate) struct ExceptionHandler {
80 pub(crate) catch_ip: usize,
81 pub(crate) stack_depth: usize,
82 pub(crate) frame_depth: usize,
83 pub(crate) env_scope_depth: usize,
84 pub(crate) error_type: String,
86}
87
88pub(crate) enum IterState {
90 Vec {
91 items: Rc<Vec<VmValue>>,
92 idx: usize,
93 },
94 Dict {
95 entries: Rc<BTreeMap<String, VmValue>>,
96 keys: Vec<String>,
97 idx: usize,
98 },
99 Channel {
100 receiver: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
101 closed: std::sync::Arc<std::sync::atomic::AtomicBool>,
102 },
103 Generator {
104 gen: crate::value::VmGenerator,
105 },
106 Stream {
107 stream: crate::value::VmStream,
108 },
109 Range {
113 next: i64,
114 stop: i64,
115 },
116 VmIter {
117 handle: std::rc::Rc<std::cell::RefCell<crate::vm::iter::VmIter>>,
118 },
119}
120
121#[derive(Clone)]
122pub(crate) enum VmBuiltinDispatch {
123 Sync(VmBuiltinFn),
124 Async(VmAsyncBuiltinFn),
125}
126
127#[derive(Clone)]
128pub(crate) struct VmBuiltinEntry {
129 pub(crate) name: Rc<str>,
130 pub(crate) dispatch: VmBuiltinDispatch,
131}
132
133pub struct Vm {
135 pub(crate) stack: Vec<VmValue>,
136 pub(crate) env: VmEnv,
137 pub(crate) output: String,
138 pub(crate) builtins: Rc<BTreeMap<String, VmBuiltinFn>>,
139 pub(crate) async_builtins: Rc<BTreeMap<String, VmAsyncBuiltinFn>>,
140 pub(crate) builtin_metadata: Rc<BTreeMap<String, VmBuiltinMetadata>>,
141 pub(crate) builtins_by_id: Rc<BTreeMap<BuiltinId, VmBuiltinEntry>>,
144 pub(crate) builtin_id_collisions: Rc<HashSet<BuiltinId>>,
147 pub(crate) iterators: Vec<IterState>,
149 pub(crate) frames: Vec<CallFrame>,
151 pub(crate) exception_handlers: Vec<ExceptionHandler>,
153 pub(crate) spawned_tasks: BTreeMap<String, VmTaskHandle>,
155 pub(crate) sync_runtime: Arc<crate::synchronization::VmSyncRuntime>,
157 pub(crate) shared_state_runtime: Rc<crate::shared_state::VmSharedStateRuntime>,
159 pub(crate) held_sync_guards: Vec<crate::synchronization::VmSyncHeldGuard>,
161 pub(crate) task_counter: u64,
163 pub(crate) runtime_context_counter: u64,
165 pub(crate) runtime_context: crate::runtime_context::RuntimeContext,
167 pub(crate) deadlines: Vec<(Instant, usize)>,
169 pub(crate) breakpoints: BTreeMap<String, std::collections::BTreeSet<usize>>,
174 pub(crate) function_breakpoints: std::collections::BTreeSet<String>,
180 pub(crate) pending_function_bp: Option<String>,
185 pub(crate) step_mode: bool,
187 pub(crate) step_frame_depth: usize,
189 pub(crate) stopped: bool,
191 pub(crate) last_line: usize,
193 pub(crate) source_dir: Option<std::path::PathBuf>,
195 pub(crate) imported_paths: Vec<std::path::PathBuf>,
197 pub(crate) module_cache: Rc<BTreeMap<std::path::PathBuf, LoadedModule>>,
199 pub(crate) source_cache: Rc<BTreeMap<std::path::PathBuf, String>>,
201 pub(crate) source_file: Option<String>,
203 pub(crate) source_text: Option<String>,
205 pub(crate) bridge: Option<Rc<crate::bridge::HostBridge>>,
207 pub(crate) denied_builtins: Rc<HashSet<String>>,
209 pub(crate) cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
211 pub(crate) cancel_grace_instructions_remaining: Option<usize>,
216 pub(crate) error_stack_trace: Vec<(String, usize, usize, Option<String>)>,
218 pub(crate) yield_sender: Option<tokio::sync::mpsc::Sender<Result<VmValue, VmError>>>,
221 pub(crate) project_root: Option<std::path::PathBuf>,
224 pub(crate) globals: Rc<BTreeMap<String, VmValue>>,
227 pub(crate) debug_hook: Option<Box<DebugHook>>,
229}
230
231impl Vm {
232 pub(crate) fn fresh_local_slots(chunk: &Chunk) -> Vec<LocalSlot> {
233 chunk
234 .local_slots
235 .iter()
236 .map(|_| LocalSlot {
237 value: VmValue::Nil,
238 initialized: false,
239 synced: false,
240 })
241 .collect()
242 }
243
244 pub(crate) fn bind_param_slots(
245 slots: &mut [LocalSlot],
246 func: &crate::chunk::CompiledFunction,
247 args: &[VmValue],
248 synced: bool,
249 ) {
250 let param_count = func.params.len();
251 for (i, _param) in func.params.iter().enumerate() {
252 if i >= slots.len() {
253 break;
254 }
255 if func.has_rest_param && i == param_count - 1 {
256 let rest_args = if i < args.len() {
257 args[i..].to_vec()
258 } else {
259 Vec::new()
260 };
261 slots[i].value = VmValue::List(Rc::new(rest_args));
262 slots[i].initialized = true;
263 slots[i].synced = synced;
264 } else if i < args.len() {
265 slots[i].value = args[i].clone();
266 slots[i].initialized = true;
267 slots[i].synced = synced;
268 }
269 }
270 }
271
272 pub(crate) fn visible_variables(&self) -> BTreeMap<String, VmValue> {
273 let mut vars = self.env.all_variables();
274 let Some(frame) = self.frames.last() else {
275 return vars;
276 };
277 for (slot, info) in frame.local_slots.iter().zip(frame.chunk.local_slots.iter()) {
278 if slot.initialized && info.scope_depth <= frame.local_scope_depth {
279 vars.insert(info.name.clone(), slot.value.clone());
280 }
281 }
282 vars
283 }
284
285 pub(crate) fn sync_current_frame_locals_to_env(&mut self) {
286 let frames = &mut self.frames;
287 let env = &mut self.env;
288 let Some(frame) = frames.last_mut() else {
289 return;
290 };
291 let local_scope_base = frame.local_scope_base;
292 let local_scope_depth = frame.local_scope_depth;
293 for (slot, info) in frame
294 .local_slots
295 .iter_mut()
296 .zip(frame.chunk.local_slots.iter())
297 {
298 if slot.initialized && !slot.synced && info.scope_depth <= local_scope_depth {
299 slot.synced = true;
300 let scope_idx = local_scope_base + info.scope_depth;
301 while env.scopes.len() <= scope_idx {
302 env.push_scope();
303 }
304 env.scopes[scope_idx]
305 .vars
306 .insert(info.name.clone(), (slot.value.clone(), info.mutable));
307 }
308 }
309 }
310
311 pub(crate) fn closure_call_env_for_current_frame(
312 &self,
313 closure: &crate::value::VmClosure,
314 ) -> VmEnv {
315 if closure.module_state.is_some() {
316 return closure.env.clone();
317 }
318 let mut call_env = Self::closure_call_env(&self.env, closure);
319 let Some(frame) = self.frames.last() else {
320 return call_env;
321 };
322 for (slot, info) in frame
323 .local_slots
324 .iter()
325 .zip(frame.chunk.local_slots.iter())
326 .filter(|(slot, info)| slot.initialized && info.scope_depth <= frame.local_scope_depth)
327 {
328 if matches!(slot.value, VmValue::Closure(_)) && !call_env.contains(&info.name) {
329 let _ = call_env.define(&info.name, slot.value.clone(), info.mutable);
330 }
331 }
332 call_env
333 }
334
335 pub(crate) fn active_local_slot_value(&self, name: &str) -> Option<VmValue> {
336 let frame = self.frames.last()?;
337 let idx = self.active_local_slot_index(name)?;
338 frame.local_slots.get(idx).map(|slot| slot.value.clone())
339 }
340
341 pub(crate) fn active_local_slot_index(&self, name: &str) -> Option<usize> {
346 let frame = self.frames.last()?;
347 for (idx, info) in frame.chunk.local_slots.iter().enumerate().rev() {
348 if info.name == name && info.scope_depth <= frame.local_scope_depth {
349 if let Some(slot) = frame.local_slots.get(idx) {
350 if slot.initialized {
351 return Some(idx);
352 }
353 }
354 }
355 }
356 None
357 }
358
359 pub(crate) fn assign_active_local_slot(
360 &mut self,
361 name: &str,
362 value: VmValue,
363 debug: bool,
364 ) -> Result<bool, VmError> {
365 let Some(frame) = self.frames.last_mut() else {
366 return Ok(false);
367 };
368 for (idx, info) in frame.chunk.local_slots.iter().enumerate().rev() {
369 if info.name == name && info.scope_depth <= frame.local_scope_depth {
370 if !debug && !info.mutable {
371 return Err(VmError::ImmutableAssignment(name.to_string()));
372 }
373 if let Some(slot) = frame.local_slots.get_mut(idx) {
374 slot.value = value;
375 slot.initialized = true;
376 slot.synced = false;
377 return Ok(true);
378 }
379 }
380 }
381 Ok(false)
382 }
383
384 pub fn new() -> Self {
385 Self {
386 stack: Vec::with_capacity(256),
387 env: VmEnv::new(),
388 output: String::new(),
389 builtins: Rc::new(BTreeMap::new()),
390 async_builtins: Rc::new(BTreeMap::new()),
391 builtin_metadata: Rc::new(BTreeMap::new()),
392 builtins_by_id: Rc::new(BTreeMap::new()),
393 builtin_id_collisions: Rc::new(HashSet::new()),
394 iterators: Vec::new(),
395 frames: Vec::new(),
396 exception_handlers: Vec::new(),
397 spawned_tasks: BTreeMap::new(),
398 sync_runtime: Arc::new(crate::synchronization::VmSyncRuntime::new()),
399 shared_state_runtime: Rc::new(crate::shared_state::VmSharedStateRuntime::new()),
400 held_sync_guards: Vec::new(),
401 task_counter: 0,
402 runtime_context_counter: 0,
403 runtime_context: crate::runtime_context::RuntimeContext::root(),
404 deadlines: Vec::new(),
405 breakpoints: BTreeMap::new(),
406 function_breakpoints: std::collections::BTreeSet::new(),
407 pending_function_bp: None,
408 step_mode: false,
409 step_frame_depth: 0,
410 stopped: false,
411 last_line: 0,
412 source_dir: None,
413 imported_paths: Vec::new(),
414 module_cache: Rc::new(BTreeMap::new()),
415 source_cache: Rc::new(BTreeMap::new()),
416 source_file: None,
417 source_text: None,
418 bridge: None,
419 denied_builtins: Rc::new(HashSet::new()),
420 cancel_token: None,
421 cancel_grace_instructions_remaining: None,
422 error_stack_trace: Vec::new(),
423 yield_sender: None,
424 project_root: None,
425 globals: Rc::new(BTreeMap::new()),
426 debug_hook: None,
427 }
428 }
429
430 pub fn set_bridge(&mut self, bridge: Rc<crate::bridge::HostBridge>) {
432 self.bridge = Some(bridge);
433 }
434
435 pub fn set_denied_builtins(&mut self, denied: HashSet<String>) {
438 self.denied_builtins = Rc::new(denied);
439 }
440
441 pub fn set_source_info(&mut self, file: &str, text: &str) {
443 self.source_file = Some(file.to_string());
444 self.source_text = Some(text.to_string());
445 Rc::make_mut(&mut self.source_cache)
446 .insert(std::path::PathBuf::from(file), text.to_string());
447 }
448
449 pub fn start(&mut self, chunk: &Chunk) {
451 let initial_env = self.env.clone();
452 self.frames.push(CallFrame {
453 chunk: Rc::new(chunk.clone()),
454 ip: 0,
455 stack_base: self.stack.len(),
456 saved_env: self.env.clone(),
457 initial_env: Some(initial_env),
462 initial_local_slots: Some(Self::fresh_local_slots(chunk)),
463 saved_iterator_depth: self.iterators.len(),
464 fn_name: String::new(),
465 argc: 0,
466 saved_source_dir: None,
467 module_functions: None,
468 module_state: None,
469 local_slots: Self::fresh_local_slots(chunk),
470 local_scope_base: self.env.scope_depth().saturating_sub(1),
471 local_scope_depth: 0,
472 });
473 }
474
475 pub(crate) fn child_vm(&self) -> Vm {
478 Vm {
479 stack: Vec::with_capacity(64),
480 env: self.env.clone(),
481 output: String::new(),
482 builtins: Rc::clone(&self.builtins),
483 async_builtins: Rc::clone(&self.async_builtins),
484 builtin_metadata: Rc::clone(&self.builtin_metadata),
485 builtins_by_id: Rc::clone(&self.builtins_by_id),
486 builtin_id_collisions: Rc::clone(&self.builtin_id_collisions),
487 iterators: Vec::new(),
488 frames: Vec::new(),
489 exception_handlers: Vec::new(),
490 spawned_tasks: BTreeMap::new(),
491 sync_runtime: self.sync_runtime.clone(),
492 shared_state_runtime: self.shared_state_runtime.clone(),
493 held_sync_guards: Vec::new(),
494 task_counter: 0,
495 runtime_context_counter: self.runtime_context_counter,
496 runtime_context: self.runtime_context.clone(),
497 deadlines: self.deadlines.clone(),
498 breakpoints: BTreeMap::new(),
499 function_breakpoints: std::collections::BTreeSet::new(),
500 pending_function_bp: None,
501 step_mode: false,
502 step_frame_depth: 0,
503 stopped: false,
504 last_line: 0,
505 source_dir: self.source_dir.clone(),
506 imported_paths: Vec::new(),
507 module_cache: Rc::clone(&self.module_cache),
508 source_cache: Rc::clone(&self.source_cache),
509 source_file: self.source_file.clone(),
510 source_text: self.source_text.clone(),
511 bridge: self.bridge.clone(),
512 denied_builtins: Rc::clone(&self.denied_builtins),
513 cancel_token: self.cancel_token.clone(),
514 cancel_grace_instructions_remaining: None,
515 error_stack_trace: Vec::new(),
516 yield_sender: None,
517 project_root: self.project_root.clone(),
518 globals: Rc::clone(&self.globals),
519 debug_hook: None,
520 }
521 }
522
523 pub(crate) fn child_vm_for_host(&self) -> Vm {
526 self.child_vm()
527 }
528
529 pub(crate) fn cancel_spawned_tasks(&mut self) {
533 for (_, task) in std::mem::take(&mut self.spawned_tasks) {
534 task.cancel_token
535 .store(true, std::sync::atomic::Ordering::SeqCst);
536 task.handle.abort();
537 }
538 }
539
540 pub fn set_source_dir(&mut self, dir: &std::path::Path) {
543 let dir = crate::stdlib::process::normalize_context_path(dir);
544 self.source_dir = Some(dir.clone());
545 crate::stdlib::set_thread_source_dir(&dir);
546 if self.project_root.is_none() {
548 self.project_root = crate::stdlib::process::find_project_root(&dir);
549 }
550 }
551
552 pub fn set_project_root(&mut self, root: &std::path::Path) {
555 self.project_root = Some(root.to_path_buf());
556 }
557
558 pub fn project_root(&self) -> Option<&std::path::Path> {
560 self.project_root.as_deref().or(self.source_dir.as_deref())
561 }
562
563 pub fn builtin_names(&self) -> Vec<String> {
565 let mut names: Vec<String> = self.builtins.keys().cloned().collect();
566 names.extend(self.async_builtins.keys().cloned());
567 names
568 }
569
570 pub fn builtin_metadata(&self) -> Vec<VmBuiltinMetadata> {
572 self.builtin_metadata.values().cloned().collect()
573 }
574
575 pub fn builtin_metadata_for(&self, name: &str) -> Option<&VmBuiltinMetadata> {
577 self.builtin_metadata.get(name)
578 }
579
580 pub fn set_global(&mut self, name: &str, value: VmValue) {
583 Rc::make_mut(&mut self.globals).insert(name.to_string(), value);
584 }
585
586 pub fn output(&self) -> &str {
588 &self.output
589 }
590
591 pub fn take_output(&mut self) -> String {
595 std::mem::take(&mut self.output)
596 }
597
598 pub fn append_output(&mut self, text: &str) {
602 self.output.push_str(text);
603 }
604
605 pub(crate) fn pop(&mut self) -> Result<VmValue, VmError> {
606 self.stack.pop().ok_or(VmError::StackUnderflow)
607 }
608
609 pub(crate) fn peek(&self) -> Result<&VmValue, VmError> {
610 self.stack.last().ok_or(VmError::StackUnderflow)
611 }
612
613 pub(crate) fn const_string(c: &Constant) -> Result<String, VmError> {
614 match c {
615 Constant::String(s) => Ok(s.clone()),
616 _ => Err(VmError::TypeError("expected string constant".into())),
617 }
618 }
619
620 pub(crate) fn const_str(c: &Constant) -> Result<&str, VmError> {
621 match c {
622 Constant::String(s) => Ok(s.as_str()),
623 _ => Err(VmError::TypeError("expected string constant".into())),
624 }
625 }
626
627 pub(crate) fn release_sync_guards_for_current_scope(&mut self) {
628 let depth = self.env.scope_depth();
629 self.held_sync_guards
630 .retain(|guard| guard.env_scope_depth < depth);
631 }
632
633 pub(crate) fn release_sync_guards_after_unwind(
634 &mut self,
635 frame_depth: usize,
636 env_scope_depth: usize,
637 ) {
638 self.held_sync_guards.retain(|guard| {
639 guard.frame_depth <= frame_depth && guard.env_scope_depth <= env_scope_depth
640 });
641 }
642
643 pub(crate) fn release_sync_guards_for_frame(&mut self, frame_depth: usize) {
644 self.held_sync_guards
645 .retain(|guard| guard.frame_depth != frame_depth);
646 }
647}
648
649impl Drop for Vm {
650 fn drop(&mut self) {
651 self.cancel_spawned_tasks();
652 }
653}
654
655impl Default for Vm {
656 fn default() -> Self {
657 Self::new()
658 }
659}