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;
14
15pub(crate) struct ScopeSpan(u64);
17
18impl ScopeSpan {
19 pub(crate) fn new(kind: crate::tracing::SpanKind, name: String) -> Self {
20 Self(crate::tracing::span_start(kind, name))
21 }
22}
23
24impl Drop for ScopeSpan {
25 fn drop(&mut self) {
26 crate::tracing::span_end(self.0);
27 }
28}
29
30#[derive(Clone)]
31pub(crate) struct LocalSlot {
32 pub(crate) value: VmValue,
33 pub(crate) initialized: bool,
34 pub(crate) synced: bool,
35}
36
37pub(crate) struct CallFrame {
39 pub(crate) chunk: ChunkRef,
40 pub(crate) ip: usize,
41 pub(crate) stack_base: usize,
42 pub(crate) saved_env: VmEnv,
43 pub(crate) initial_env: Option<VmEnv>,
51 pub(crate) initial_local_slots: Option<Vec<LocalSlot>>,
52 pub(crate) saved_iterator_depth: usize,
54 pub(crate) fn_name: String,
56 pub(crate) argc: usize,
58 pub(crate) saved_source_dir: Option<std::path::PathBuf>,
61 pub(crate) module_functions: Option<ModuleFunctionRegistry>,
63 pub(crate) module_state: Option<crate::value::ModuleState>,
69 pub(crate) local_slots: Vec<LocalSlot>,
71 pub(crate) local_scope_base: usize,
73 pub(crate) local_scope_depth: usize,
75}
76
77pub(crate) struct ExceptionHandler {
79 pub(crate) catch_ip: usize,
80 pub(crate) stack_depth: usize,
81 pub(crate) frame_depth: usize,
82 pub(crate) env_scope_depth: usize,
83 pub(crate) error_type: String,
85}
86
87pub(crate) enum IterState {
89 Vec {
90 items: Rc<Vec<VmValue>>,
91 idx: usize,
92 },
93 Dict {
94 entries: Rc<BTreeMap<String, VmValue>>,
95 keys: Vec<String>,
96 idx: usize,
97 },
98 Channel {
99 receiver: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
100 closed: std::sync::Arc<std::sync::atomic::AtomicBool>,
101 },
102 Generator {
103 gen: crate::value::VmGenerator,
104 },
105 Stream {
106 stream: crate::value::VmStream,
107 },
108 Range {
112 next: i64,
113 stop: i64,
114 },
115 VmIter {
116 handle: std::rc::Rc<std::cell::RefCell<crate::vm::iter::VmIter>>,
117 },
118}
119
120#[derive(Clone)]
121pub(crate) enum VmBuiltinDispatch {
122 Sync(VmBuiltinFn),
123 Async(VmAsyncBuiltinFn),
124}
125
126#[derive(Clone)]
127pub(crate) struct VmBuiltinEntry {
128 pub(crate) name: Rc<str>,
129 pub(crate) dispatch: VmBuiltinDispatch,
130}
131
132pub struct Vm {
134 pub(crate) stack: Vec<VmValue>,
135 pub(crate) env: VmEnv,
136 pub(crate) output: String,
137 pub(crate) builtins: BTreeMap<String, VmBuiltinFn>,
138 pub(crate) async_builtins: BTreeMap<String, VmAsyncBuiltinFn>,
139 pub(crate) builtins_by_id: BTreeMap<BuiltinId, VmBuiltinEntry>,
142 pub(crate) builtin_id_collisions: HashSet<BuiltinId>,
145 pub(crate) iterators: Vec<IterState>,
147 pub(crate) frames: Vec<CallFrame>,
149 pub(crate) exception_handlers: Vec<ExceptionHandler>,
151 pub(crate) spawned_tasks: BTreeMap<String, VmTaskHandle>,
153 pub(crate) sync_runtime: Arc<crate::synchronization::VmSyncRuntime>,
155 pub(crate) shared_state_runtime: Rc<crate::shared_state::VmSharedStateRuntime>,
157 pub(crate) held_sync_guards: Vec<crate::synchronization::VmSyncHeldGuard>,
159 pub(crate) task_counter: u64,
161 pub(crate) runtime_context_counter: u64,
163 pub(crate) runtime_context: crate::runtime_context::RuntimeContext,
165 pub(crate) deadlines: Vec<(Instant, usize)>,
167 pub(crate) breakpoints: BTreeMap<String, std::collections::BTreeSet<usize>>,
172 pub(crate) function_breakpoints: std::collections::BTreeSet<String>,
178 pub(crate) pending_function_bp: Option<String>,
183 pub(crate) step_mode: bool,
185 pub(crate) step_frame_depth: usize,
187 pub(crate) stopped: bool,
189 pub(crate) last_line: usize,
191 pub(crate) source_dir: Option<std::path::PathBuf>,
193 pub(crate) imported_paths: Vec<std::path::PathBuf>,
195 pub(crate) module_cache: BTreeMap<std::path::PathBuf, LoadedModule>,
197 pub(crate) source_cache: BTreeMap<std::path::PathBuf, String>,
199 pub(crate) source_file: Option<String>,
201 pub(crate) source_text: Option<String>,
203 pub(crate) bridge: Option<Rc<crate::bridge::HostBridge>>,
205 pub(crate) denied_builtins: HashSet<String>,
207 pub(crate) cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
209 pub(crate) cancel_grace_instructions_remaining: Option<usize>,
214 pub(crate) error_stack_trace: Vec<(String, usize, usize, Option<String>)>,
216 pub(crate) yield_sender: Option<tokio::sync::mpsc::Sender<Result<VmValue, VmError>>>,
219 pub(crate) project_root: Option<std::path::PathBuf>,
222 pub(crate) globals: BTreeMap<String, VmValue>,
225 pub(crate) debug_hook: Option<Box<DebugHook>>,
227}
228
229impl Vm {
230 pub(crate) fn fresh_local_slots(chunk: &Chunk) -> Vec<LocalSlot> {
231 chunk
232 .local_slots
233 .iter()
234 .map(|_| LocalSlot {
235 value: VmValue::Nil,
236 initialized: false,
237 synced: false,
238 })
239 .collect()
240 }
241
242 pub(crate) fn bind_param_slots(
243 slots: &mut [LocalSlot],
244 func: &crate::chunk::CompiledFunction,
245 args: &[VmValue],
246 synced: bool,
247 ) {
248 let default_start = func.default_start.unwrap_or(func.params.len());
249 let param_count = func.params.len();
250 for (i, _param) in func.params.iter().enumerate() {
251 if i >= slots.len() {
252 break;
253 }
254 if func.has_rest_param && i == param_count - 1 {
255 let rest_args = if i < args.len() {
256 args[i..].to_vec()
257 } else {
258 Vec::new()
259 };
260 slots[i].value = VmValue::List(Rc::new(rest_args));
261 slots[i].initialized = true;
262 slots[i].synced = synced;
263 } else if i < args.len() {
264 slots[i].value = args[i].clone();
265 slots[i].initialized = true;
266 slots[i].synced = synced;
267 } else if i < default_start {
268 slots[i].value = VmValue::Nil;
269 slots[i].initialized = true;
270 slots[i].synced = synced;
271 }
272 }
273 }
274
275 pub(crate) fn visible_variables(&self) -> BTreeMap<String, VmValue> {
276 let mut vars = self.env.all_variables();
277 let Some(frame) = self.frames.last() else {
278 return vars;
279 };
280 for (slot, info) in frame.local_slots.iter().zip(frame.chunk.local_slots.iter()) {
281 if slot.initialized && info.scope_depth <= frame.local_scope_depth {
282 vars.insert(info.name.clone(), slot.value.clone());
283 }
284 }
285 vars
286 }
287
288 pub(crate) fn sync_current_frame_locals_to_env(&mut self) {
289 let Some(frame) = self.frames.last_mut() else {
290 return;
291 };
292 let local_scope_base = frame.local_scope_base;
293 let local_scope_depth = frame.local_scope_depth;
294 let entries = frame
295 .local_slots
296 .iter_mut()
297 .zip(frame.chunk.local_slots.iter())
298 .filter_map(|(slot, info)| {
299 if slot.initialized && !slot.synced && info.scope_depth <= local_scope_depth {
300 slot.synced = true;
301 Some((
302 local_scope_base + info.scope_depth,
303 info.name.clone(),
304 slot.value.clone(),
305 info.mutable,
306 ))
307 } else {
308 None
309 }
310 })
311 .collect::<Vec<_>>();
312 for (scope_idx, name, value, mutable) in entries {
313 while self.env.scopes.len() <= scope_idx {
314 self.env.push_scope();
315 }
316 self.env.scopes[scope_idx]
317 .vars
318 .insert(name, (value, mutable));
319 }
320 }
321
322 pub(crate) fn closure_call_env_for_current_frame(
323 &self,
324 closure: &crate::value::VmClosure,
325 ) -> VmEnv {
326 if closure.module_state.is_some() {
327 return closure.env.clone();
328 }
329 let mut call_env = Self::closure_call_env(&self.env, closure);
330 let Some(frame) = self.frames.last() else {
331 return call_env;
332 };
333 for (slot, info) in frame
334 .local_slots
335 .iter()
336 .zip(frame.chunk.local_slots.iter())
337 .filter(|(slot, info)| slot.initialized && info.scope_depth <= frame.local_scope_depth)
338 {
339 if matches!(slot.value, VmValue::Closure(_)) && call_env.get(&info.name).is_none() {
340 let _ = call_env.define(&info.name, slot.value.clone(), info.mutable);
341 }
342 }
343 call_env
344 }
345
346 pub(crate) fn active_local_slot_value(&self, name: &str) -> Option<VmValue> {
347 let frame = self.frames.last()?;
348 for (idx, info) in frame.chunk.local_slots.iter().enumerate().rev() {
349 if info.name == name && info.scope_depth <= frame.local_scope_depth {
350 let slot = frame.local_slots.get(idx)?;
351 if slot.initialized {
352 return Some(slot.value.clone());
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: BTreeMap::new(),
390 async_builtins: BTreeMap::new(),
391 builtins_by_id: BTreeMap::new(),
392 builtin_id_collisions: HashSet::new(),
393 iterators: Vec::new(),
394 frames: Vec::new(),
395 exception_handlers: Vec::new(),
396 spawned_tasks: BTreeMap::new(),
397 sync_runtime: Arc::new(crate::synchronization::VmSyncRuntime::new()),
398 shared_state_runtime: Rc::new(crate::shared_state::VmSharedStateRuntime::new()),
399 held_sync_guards: Vec::new(),
400 task_counter: 0,
401 runtime_context_counter: 0,
402 runtime_context: crate::runtime_context::RuntimeContext::root(),
403 deadlines: Vec::new(),
404 breakpoints: BTreeMap::new(),
405 function_breakpoints: std::collections::BTreeSet::new(),
406 pending_function_bp: None,
407 step_mode: false,
408 step_frame_depth: 0,
409 stopped: false,
410 last_line: 0,
411 source_dir: None,
412 imported_paths: Vec::new(),
413 module_cache: BTreeMap::new(),
414 source_cache: BTreeMap::new(),
415 source_file: None,
416 source_text: None,
417 bridge: None,
418 denied_builtins: HashSet::new(),
419 cancel_token: None,
420 cancel_grace_instructions_remaining: None,
421 error_stack_trace: Vec::new(),
422 yield_sender: None,
423 project_root: None,
424 globals: BTreeMap::new(),
425 debug_hook: None,
426 }
427 }
428
429 pub fn set_bridge(&mut self, bridge: Rc<crate::bridge::HostBridge>) {
431 self.bridge = Some(bridge);
432 }
433
434 pub fn set_denied_builtins(&mut self, denied: HashSet<String>) {
437 self.denied_builtins = denied;
438 }
439
440 pub fn set_source_info(&mut self, file: &str, text: &str) {
442 self.source_file = Some(file.to_string());
443 self.source_text = Some(text.to_string());
444 self.source_cache
445 .insert(std::path::PathBuf::from(file), text.to_string());
446 }
447
448 pub fn start(&mut self, chunk: &Chunk) {
450 let initial_env = self.env.clone();
451 self.frames.push(CallFrame {
452 chunk: Rc::new(chunk.clone()),
453 ip: 0,
454 stack_base: self.stack.len(),
455 saved_env: self.env.clone(),
456 initial_env: Some(initial_env),
461 initial_local_slots: Some(Self::fresh_local_slots(chunk)),
462 saved_iterator_depth: self.iterators.len(),
463 fn_name: String::new(),
464 argc: 0,
465 saved_source_dir: None,
466 module_functions: None,
467 module_state: None,
468 local_slots: Self::fresh_local_slots(chunk),
469 local_scope_base: self.env.scope_depth().saturating_sub(1),
470 local_scope_depth: 0,
471 });
472 }
473
474 pub(crate) fn child_vm(&self) -> Vm {
477 Vm {
478 stack: Vec::with_capacity(64),
479 env: self.env.clone(),
480 output: String::new(),
481 builtins: self.builtins.clone(),
482 async_builtins: self.async_builtins.clone(),
483 builtins_by_id: self.builtins_by_id.clone(),
484 builtin_id_collisions: self.builtin_id_collisions.clone(),
485 iterators: Vec::new(),
486 frames: Vec::new(),
487 exception_handlers: Vec::new(),
488 spawned_tasks: BTreeMap::new(),
489 sync_runtime: self.sync_runtime.clone(),
490 shared_state_runtime: self.shared_state_runtime.clone(),
491 held_sync_guards: Vec::new(),
492 task_counter: 0,
493 runtime_context_counter: self.runtime_context_counter,
494 runtime_context: self.runtime_context.clone(),
495 deadlines: self.deadlines.clone(),
496 breakpoints: BTreeMap::new(),
497 function_breakpoints: std::collections::BTreeSet::new(),
498 pending_function_bp: None,
499 step_mode: false,
500 step_frame_depth: 0,
501 stopped: false,
502 last_line: 0,
503 source_dir: self.source_dir.clone(),
504 imported_paths: Vec::new(),
505 module_cache: self.module_cache.clone(),
506 source_cache: self.source_cache.clone(),
507 source_file: self.source_file.clone(),
508 source_text: self.source_text.clone(),
509 bridge: self.bridge.clone(),
510 denied_builtins: self.denied_builtins.clone(),
511 cancel_token: self.cancel_token.clone(),
512 cancel_grace_instructions_remaining: None,
513 error_stack_trace: Vec::new(),
514 yield_sender: None,
515 project_root: self.project_root.clone(),
516 globals: self.globals.clone(),
517 debug_hook: None,
518 }
519 }
520
521 pub(crate) fn child_vm_for_host(&self) -> Vm {
524 self.child_vm()
525 }
526
527 pub(crate) fn cancel_spawned_tasks(&mut self) {
531 for (_, task) in std::mem::take(&mut self.spawned_tasks) {
532 task.cancel_token
533 .store(true, std::sync::atomic::Ordering::SeqCst);
534 task.handle.abort();
535 }
536 }
537
538 pub fn set_source_dir(&mut self, dir: &std::path::Path) {
541 let dir = crate::stdlib::process::normalize_context_path(dir);
542 self.source_dir = Some(dir.clone());
543 crate::stdlib::set_thread_source_dir(&dir);
544 if self.project_root.is_none() {
546 self.project_root = crate::stdlib::process::find_project_root(&dir);
547 }
548 }
549
550 pub fn set_project_root(&mut self, root: &std::path::Path) {
553 self.project_root = Some(root.to_path_buf());
554 }
555
556 pub fn project_root(&self) -> Option<&std::path::Path> {
558 self.project_root.as_deref().or(self.source_dir.as_deref())
559 }
560
561 pub fn builtin_names(&self) -> Vec<String> {
563 let mut names: Vec<String> = self.builtins.keys().cloned().collect();
564 names.extend(self.async_builtins.keys().cloned());
565 names
566 }
567
568 pub fn set_global(&mut self, name: &str, value: VmValue) {
571 self.globals.insert(name.to_string(), value);
572 }
573
574 pub fn output(&self) -> &str {
576 &self.output
577 }
578
579 pub(crate) fn pop(&mut self) -> Result<VmValue, VmError> {
580 self.stack.pop().ok_or(VmError::StackUnderflow)
581 }
582
583 pub(crate) fn peek(&self) -> Result<&VmValue, VmError> {
584 self.stack.last().ok_or(VmError::StackUnderflow)
585 }
586
587 pub(crate) fn const_string(c: &Constant) -> Result<String, VmError> {
588 match c {
589 Constant::String(s) => Ok(s.clone()),
590 _ => Err(VmError::TypeError("expected string constant".into())),
591 }
592 }
593
594 pub(crate) fn release_sync_guards_for_current_scope(&mut self) {
595 let depth = self.env.scope_depth();
596 self.held_sync_guards
597 .retain(|guard| guard.env_scope_depth < depth);
598 }
599
600 pub(crate) fn release_sync_guards_after_unwind(
601 &mut self,
602 frame_depth: usize,
603 env_scope_depth: usize,
604 ) {
605 self.held_sync_guards.retain(|guard| {
606 guard.frame_depth <= frame_depth && guard.env_scope_depth <= env_scope_depth
607 });
608 }
609
610 pub(crate) fn release_sync_guards_for_frame(&mut self, frame_depth: usize) {
611 self.held_sync_guards
612 .retain(|guard| guard.frame_depth != frame_depth);
613 }
614}
615
616impl Drop for Vm {
617 fn drop(&mut self) {
618 self.cancel_spawned_tasks();
619 }
620}
621
622impl Default for Vm {
623 fn default() -> Self {
624 Self::new()
625 }
626}