use std::collections::{BTreeMap, HashSet};
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use crate::chunk::{Chunk, ChunkRef, Constant};
use crate::value::{
ModuleFunctionRegistry, VmAsyncBuiltinFn, VmBuiltinFn, VmEnv, VmError, VmTaskHandle, VmValue,
};
use crate::BuiltinId;
use super::debug::DebugHook;
use super::modules::LoadedModule;
pub(crate) struct ScopeSpan(u64);
impl ScopeSpan {
pub(crate) fn new(kind: crate::tracing::SpanKind, name: String) -> Self {
Self(crate::tracing::span_start(kind, name))
}
}
impl Drop for ScopeSpan {
fn drop(&mut self) {
crate::tracing::span_end(self.0);
}
}
#[derive(Clone)]
pub(crate) struct LocalSlot {
pub(crate) value: VmValue,
pub(crate) initialized: bool,
pub(crate) synced: bool,
}
pub(crate) struct CallFrame {
pub(crate) chunk: ChunkRef,
pub(crate) ip: usize,
pub(crate) stack_base: usize,
pub(crate) saved_env: VmEnv,
pub(crate) initial_env: Option<VmEnv>,
pub(crate) initial_local_slots: Option<Vec<LocalSlot>>,
pub(crate) saved_iterator_depth: usize,
pub(crate) fn_name: String,
pub(crate) argc: usize,
pub(crate) saved_source_dir: Option<std::path::PathBuf>,
pub(crate) module_functions: Option<ModuleFunctionRegistry>,
pub(crate) module_state: Option<crate::value::ModuleState>,
pub(crate) local_slots: Vec<LocalSlot>,
pub(crate) local_scope_base: usize,
pub(crate) local_scope_depth: usize,
}
pub(crate) struct ExceptionHandler {
pub(crate) catch_ip: usize,
pub(crate) stack_depth: usize,
pub(crate) frame_depth: usize,
pub(crate) env_scope_depth: usize,
pub(crate) error_type: String,
}
pub(crate) enum IterState {
Vec {
items: Rc<Vec<VmValue>>,
idx: usize,
},
Dict {
entries: Rc<BTreeMap<String, VmValue>>,
keys: Vec<String>,
idx: usize,
},
Channel {
receiver: std::sync::Arc<tokio::sync::Mutex<tokio::sync::mpsc::Receiver<VmValue>>>,
closed: std::sync::Arc<std::sync::atomic::AtomicBool>,
},
Generator {
gen: crate::value::VmGenerator,
},
Stream {
stream: crate::value::VmStream,
},
Range {
next: i64,
stop: i64,
},
VmIter {
handle: std::rc::Rc<std::cell::RefCell<crate::vm::iter::VmIter>>,
},
}
#[derive(Clone)]
pub(crate) enum VmBuiltinDispatch {
Sync(VmBuiltinFn),
Async(VmAsyncBuiltinFn),
}
#[derive(Clone)]
pub(crate) struct VmBuiltinEntry {
pub(crate) name: Rc<str>,
pub(crate) dispatch: VmBuiltinDispatch,
}
pub struct Vm {
pub(crate) stack: Vec<VmValue>,
pub(crate) env: VmEnv,
pub(crate) output: String,
pub(crate) builtins: BTreeMap<String, VmBuiltinFn>,
pub(crate) async_builtins: BTreeMap<String, VmAsyncBuiltinFn>,
pub(crate) builtins_by_id: BTreeMap<BuiltinId, VmBuiltinEntry>,
pub(crate) builtin_id_collisions: HashSet<BuiltinId>,
pub(crate) iterators: Vec<IterState>,
pub(crate) frames: Vec<CallFrame>,
pub(crate) exception_handlers: Vec<ExceptionHandler>,
pub(crate) spawned_tasks: BTreeMap<String, VmTaskHandle>,
pub(crate) sync_runtime: Arc<crate::synchronization::VmSyncRuntime>,
pub(crate) shared_state_runtime: Rc<crate::shared_state::VmSharedStateRuntime>,
pub(crate) held_sync_guards: Vec<crate::synchronization::VmSyncHeldGuard>,
pub(crate) task_counter: u64,
pub(crate) runtime_context_counter: u64,
pub(crate) runtime_context: crate::runtime_context::RuntimeContext,
pub(crate) deadlines: Vec<(Instant, usize)>,
pub(crate) breakpoints: BTreeMap<String, std::collections::BTreeSet<usize>>,
pub(crate) function_breakpoints: std::collections::BTreeSet<String>,
pub(crate) pending_function_bp: Option<String>,
pub(crate) step_mode: bool,
pub(crate) step_frame_depth: usize,
pub(crate) stopped: bool,
pub(crate) last_line: usize,
pub(crate) source_dir: Option<std::path::PathBuf>,
pub(crate) imported_paths: Vec<std::path::PathBuf>,
pub(crate) module_cache: BTreeMap<std::path::PathBuf, LoadedModule>,
pub(crate) source_cache: BTreeMap<std::path::PathBuf, String>,
pub(crate) source_file: Option<String>,
pub(crate) source_text: Option<String>,
pub(crate) bridge: Option<Rc<crate::bridge::HostBridge>>,
pub(crate) denied_builtins: HashSet<String>,
pub(crate) cancel_token: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
pub(crate) cancel_grace_instructions_remaining: Option<usize>,
pub(crate) error_stack_trace: Vec<(String, usize, usize, Option<String>)>,
pub(crate) yield_sender: Option<tokio::sync::mpsc::Sender<Result<VmValue, VmError>>>,
pub(crate) project_root: Option<std::path::PathBuf>,
pub(crate) globals: BTreeMap<String, VmValue>,
pub(crate) debug_hook: Option<Box<DebugHook>>,
}
impl Vm {
pub(crate) fn fresh_local_slots(chunk: &Chunk) -> Vec<LocalSlot> {
chunk
.local_slots
.iter()
.map(|_| LocalSlot {
value: VmValue::Nil,
initialized: false,
synced: false,
})
.collect()
}
pub(crate) fn bind_param_slots(
slots: &mut [LocalSlot],
func: &crate::chunk::CompiledFunction,
args: &[VmValue],
synced: bool,
) {
let default_start = func.default_start.unwrap_or(func.params.len());
let param_count = func.params.len();
for (i, _param) in func.params.iter().enumerate() {
if i >= slots.len() {
break;
}
if func.has_rest_param && i == param_count - 1 {
let rest_args = if i < args.len() {
args[i..].to_vec()
} else {
Vec::new()
};
slots[i].value = VmValue::List(Rc::new(rest_args));
slots[i].initialized = true;
slots[i].synced = synced;
} else if i < args.len() {
slots[i].value = args[i].clone();
slots[i].initialized = true;
slots[i].synced = synced;
} else if i < default_start {
slots[i].value = VmValue::Nil;
slots[i].initialized = true;
slots[i].synced = synced;
}
}
}
pub(crate) fn visible_variables(&self) -> BTreeMap<String, VmValue> {
let mut vars = self.env.all_variables();
let Some(frame) = self.frames.last() else {
return vars;
};
for (slot, info) in frame.local_slots.iter().zip(frame.chunk.local_slots.iter()) {
if slot.initialized && info.scope_depth <= frame.local_scope_depth {
vars.insert(info.name.clone(), slot.value.clone());
}
}
vars
}
pub(crate) fn sync_current_frame_locals_to_env(&mut self) {
let Some(frame) = self.frames.last_mut() else {
return;
};
let local_scope_base = frame.local_scope_base;
let local_scope_depth = frame.local_scope_depth;
let entries = frame
.local_slots
.iter_mut()
.zip(frame.chunk.local_slots.iter())
.filter_map(|(slot, info)| {
if slot.initialized && !slot.synced && info.scope_depth <= local_scope_depth {
slot.synced = true;
Some((
local_scope_base + info.scope_depth,
info.name.clone(),
slot.value.clone(),
info.mutable,
))
} else {
None
}
})
.collect::<Vec<_>>();
for (scope_idx, name, value, mutable) in entries {
while self.env.scopes.len() <= scope_idx {
self.env.push_scope();
}
self.env.scopes[scope_idx]
.vars
.insert(name, (value, mutable));
}
}
pub(crate) fn closure_call_env_for_current_frame(
&self,
closure: &crate::value::VmClosure,
) -> VmEnv {
if closure.module_state.is_some() {
return closure.env.clone();
}
let mut call_env = Self::closure_call_env(&self.env, closure);
let Some(frame) = self.frames.last() else {
return call_env;
};
for (slot, info) in frame
.local_slots
.iter()
.zip(frame.chunk.local_slots.iter())
.filter(|(slot, info)| slot.initialized && info.scope_depth <= frame.local_scope_depth)
{
if matches!(slot.value, VmValue::Closure(_)) && call_env.get(&info.name).is_none() {
let _ = call_env.define(&info.name, slot.value.clone(), info.mutable);
}
}
call_env
}
pub(crate) fn active_local_slot_value(&self, name: &str) -> Option<VmValue> {
let frame = self.frames.last()?;
for (idx, info) in frame.chunk.local_slots.iter().enumerate().rev() {
if info.name == name && info.scope_depth <= frame.local_scope_depth {
let slot = frame.local_slots.get(idx)?;
if slot.initialized {
return Some(slot.value.clone());
}
}
}
None
}
pub(crate) fn assign_active_local_slot(
&mut self,
name: &str,
value: VmValue,
debug: bool,
) -> Result<bool, VmError> {
let Some(frame) = self.frames.last_mut() else {
return Ok(false);
};
for (idx, info) in frame.chunk.local_slots.iter().enumerate().rev() {
if info.name == name && info.scope_depth <= frame.local_scope_depth {
if !debug && !info.mutable {
return Err(VmError::ImmutableAssignment(name.to_string()));
}
if let Some(slot) = frame.local_slots.get_mut(idx) {
slot.value = value;
slot.initialized = true;
slot.synced = false;
return Ok(true);
}
}
}
Ok(false)
}
pub fn new() -> Self {
Self {
stack: Vec::with_capacity(256),
env: VmEnv::new(),
output: String::new(),
builtins: BTreeMap::new(),
async_builtins: BTreeMap::new(),
builtins_by_id: BTreeMap::new(),
builtin_id_collisions: HashSet::new(),
iterators: Vec::new(),
frames: Vec::new(),
exception_handlers: Vec::new(),
spawned_tasks: BTreeMap::new(),
sync_runtime: Arc::new(crate::synchronization::VmSyncRuntime::new()),
shared_state_runtime: Rc::new(crate::shared_state::VmSharedStateRuntime::new()),
held_sync_guards: Vec::new(),
task_counter: 0,
runtime_context_counter: 0,
runtime_context: crate::runtime_context::RuntimeContext::root(),
deadlines: Vec::new(),
breakpoints: BTreeMap::new(),
function_breakpoints: std::collections::BTreeSet::new(),
pending_function_bp: None,
step_mode: false,
step_frame_depth: 0,
stopped: false,
last_line: 0,
source_dir: None,
imported_paths: Vec::new(),
module_cache: BTreeMap::new(),
source_cache: BTreeMap::new(),
source_file: None,
source_text: None,
bridge: None,
denied_builtins: HashSet::new(),
cancel_token: None,
cancel_grace_instructions_remaining: None,
error_stack_trace: Vec::new(),
yield_sender: None,
project_root: None,
globals: BTreeMap::new(),
debug_hook: None,
}
}
pub fn set_bridge(&mut self, bridge: Rc<crate::bridge::HostBridge>) {
self.bridge = Some(bridge);
}
pub fn set_denied_builtins(&mut self, denied: HashSet<String>) {
self.denied_builtins = denied;
}
pub fn set_source_info(&mut self, file: &str, text: &str) {
self.source_file = Some(file.to_string());
self.source_text = Some(text.to_string());
self.source_cache
.insert(std::path::PathBuf::from(file), text.to_string());
}
pub fn start(&mut self, chunk: &Chunk) {
let initial_env = self.env.clone();
self.frames.push(CallFrame {
chunk: Rc::new(chunk.clone()),
ip: 0,
stack_base: self.stack.len(),
saved_env: self.env.clone(),
initial_env: Some(initial_env),
initial_local_slots: Some(Self::fresh_local_slots(chunk)),
saved_iterator_depth: self.iterators.len(),
fn_name: String::new(),
argc: 0,
saved_source_dir: None,
module_functions: None,
module_state: None,
local_slots: Self::fresh_local_slots(chunk),
local_scope_base: self.env.scope_depth().saturating_sub(1),
local_scope_depth: 0,
});
}
pub(crate) fn child_vm(&self) -> Vm {
Vm {
stack: Vec::with_capacity(64),
env: self.env.clone(),
output: String::new(),
builtins: self.builtins.clone(),
async_builtins: self.async_builtins.clone(),
builtins_by_id: self.builtins_by_id.clone(),
builtin_id_collisions: self.builtin_id_collisions.clone(),
iterators: Vec::new(),
frames: Vec::new(),
exception_handlers: Vec::new(),
spawned_tasks: BTreeMap::new(),
sync_runtime: self.sync_runtime.clone(),
shared_state_runtime: self.shared_state_runtime.clone(),
held_sync_guards: Vec::new(),
task_counter: 0,
runtime_context_counter: self.runtime_context_counter,
runtime_context: self.runtime_context.clone(),
deadlines: self.deadlines.clone(),
breakpoints: BTreeMap::new(),
function_breakpoints: std::collections::BTreeSet::new(),
pending_function_bp: None,
step_mode: false,
step_frame_depth: 0,
stopped: false,
last_line: 0,
source_dir: self.source_dir.clone(),
imported_paths: Vec::new(),
module_cache: self.module_cache.clone(),
source_cache: self.source_cache.clone(),
source_file: self.source_file.clone(),
source_text: self.source_text.clone(),
bridge: self.bridge.clone(),
denied_builtins: self.denied_builtins.clone(),
cancel_token: self.cancel_token.clone(),
cancel_grace_instructions_remaining: None,
error_stack_trace: Vec::new(),
yield_sender: None,
project_root: self.project_root.clone(),
globals: self.globals.clone(),
debug_hook: None,
}
}
pub(crate) fn child_vm_for_host(&self) -> Vm {
self.child_vm()
}
pub(crate) fn cancel_spawned_tasks(&mut self) {
for (_, task) in std::mem::take(&mut self.spawned_tasks) {
task.cancel_token
.store(true, std::sync::atomic::Ordering::SeqCst);
task.handle.abort();
}
}
pub fn set_source_dir(&mut self, dir: &std::path::Path) {
let dir = crate::stdlib::process::normalize_context_path(dir);
self.source_dir = Some(dir.clone());
crate::stdlib::set_thread_source_dir(&dir);
if self.project_root.is_none() {
self.project_root = crate::stdlib::process::find_project_root(&dir);
}
}
pub fn set_project_root(&mut self, root: &std::path::Path) {
self.project_root = Some(root.to_path_buf());
}
pub fn project_root(&self) -> Option<&std::path::Path> {
self.project_root.as_deref().or(self.source_dir.as_deref())
}
pub fn builtin_names(&self) -> Vec<String> {
let mut names: Vec<String> = self.builtins.keys().cloned().collect();
names.extend(self.async_builtins.keys().cloned());
names
}
pub fn set_global(&mut self, name: &str, value: VmValue) {
self.globals.insert(name.to_string(), value);
}
pub fn output(&self) -> &str {
&self.output
}
pub(crate) fn pop(&mut self) -> Result<VmValue, VmError> {
self.stack.pop().ok_or(VmError::StackUnderflow)
}
pub(crate) fn peek(&self) -> Result<&VmValue, VmError> {
self.stack.last().ok_or(VmError::StackUnderflow)
}
pub(crate) fn const_string(c: &Constant) -> Result<String, VmError> {
match c {
Constant::String(s) => Ok(s.clone()),
_ => Err(VmError::TypeError("expected string constant".into())),
}
}
pub(crate) fn release_sync_guards_for_current_scope(&mut self) {
let depth = self.env.scope_depth();
self.held_sync_guards
.retain(|guard| guard.env_scope_depth < depth);
}
pub(crate) fn release_sync_guards_after_unwind(
&mut self,
frame_depth: usize,
env_scope_depth: usize,
) {
self.held_sync_guards.retain(|guard| {
guard.frame_depth <= frame_depth && guard.env_scope_depth <= env_scope_depth
});
}
pub(crate) fn release_sync_guards_for_frame(&mut self, frame_depth: usize) {
self.held_sync_guards
.retain(|guard| guard.frame_depth != frame_depth);
}
}
impl Drop for Vm {
fn drop(&mut self) {
self.cancel_spawned_tasks();
}
}
impl Default for Vm {
fn default() -> Self {
Self::new()
}
}