Skip to main content

harn_vm/vm/
execution.rs

1use std::rc::Rc;
2use std::time::Instant;
3
4use crate::chunk::{Chunk, ChunkRef};
5use crate::value::{ModuleFunctionRegistry, VmError, VmValue};
6
7use super::{CallFrame, LocalSlot, Vm};
8
9impl Vm {
10    /// Execute a compiled chunk.
11    pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
12        let span_id = crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "main".into());
13        let result = self.run_chunk(chunk).await;
14        crate::tracing::span_end(span_id);
15        result
16    }
17
18    /// Convert a VmError into either a handled exception (returning Ok) or a propagated error.
19    pub(crate) fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
20        let thrown_value = match &error {
21            VmError::Thrown(v) => v.clone(),
22            other => VmValue::String(Rc::from(other.to_string())),
23        };
24
25        if let Some(handler) = self.exception_handlers.pop() {
26            if !handler.error_type.is_empty() {
27                // Typed catch: only match when the thrown enum's type equals the declared type.
28                let matches = match &thrown_value {
29                    VmValue::EnumVariant { enum_name, .. } => {
30                        enum_name.as_ref() == handler.error_type
31                    }
32                    _ => false,
33                };
34                if !matches {
35                    return self.handle_error(error);
36                }
37            }
38
39            self.release_sync_guards_after_unwind(handler.frame_depth, handler.env_scope_depth);
40
41            while self.frames.len() > handler.frame_depth {
42                if let Some(frame) = self.frames.pop() {
43                    if let Some(ref dir) = frame.saved_source_dir {
44                        crate::stdlib::set_thread_source_dir(dir);
45                    }
46                    self.iterators.truncate(frame.saved_iterator_depth);
47                    self.env = frame.saved_env;
48                }
49            }
50
51            // Drop deadlines that belonged to unwound frames.
52            while self
53                .deadlines
54                .last()
55                .is_some_and(|d| d.1 > handler.frame_depth)
56            {
57                self.deadlines.pop();
58            }
59
60            self.env.truncate_scopes(handler.env_scope_depth);
61
62            self.stack.truncate(handler.stack_depth);
63            self.stack.push(thrown_value);
64
65            if let Some(frame) = self.frames.last_mut() {
66                frame.ip = handler.catch_ip;
67            }
68
69            Ok(None)
70        } else {
71            Err(error)
72        }
73    }
74
75    pub(crate) async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
76        self.run_chunk_entry(chunk, 0, None, None, None, None).await
77    }
78
79    pub(crate) async fn run_chunk_entry(
80        &mut self,
81        chunk: &Chunk,
82        argc: usize,
83        saved_source_dir: Option<std::path::PathBuf>,
84        module_functions: Option<ModuleFunctionRegistry>,
85        module_state: Option<crate::value::ModuleState>,
86        local_slots: Option<Vec<LocalSlot>>,
87    ) -> Result<VmValue, VmError> {
88        self.run_chunk_ref(
89            Rc::new(chunk.clone()),
90            argc,
91            saved_source_dir,
92            module_functions,
93            module_state,
94            local_slots,
95        )
96        .await
97    }
98
99    pub(crate) async fn run_chunk_ref(
100        &mut self,
101        chunk: ChunkRef,
102        argc: usize,
103        saved_source_dir: Option<std::path::PathBuf>,
104        module_functions: Option<ModuleFunctionRegistry>,
105        module_state: Option<crate::value::ModuleState>,
106        local_slots: Option<Vec<LocalSlot>>,
107    ) -> Result<VmValue, VmError> {
108        let initial_env = self.env.clone();
109        let local_slots = local_slots.unwrap_or_else(|| Self::fresh_local_slots(&chunk));
110        let initial_local_slots = local_slots.clone();
111        self.frames.push(CallFrame {
112            chunk,
113            ip: 0,
114            stack_base: self.stack.len(),
115            saved_env: self.env.clone(),
116            initial_env: Some(initial_env),
117            initial_local_slots: Some(initial_local_slots),
118            saved_iterator_depth: self.iterators.len(),
119            fn_name: String::new(),
120            argc,
121            saved_source_dir,
122            module_functions,
123            module_state,
124            local_slots,
125            local_scope_base: self.env.scope_depth().saturating_sub(1),
126            local_scope_depth: 0,
127        });
128
129        loop {
130            if let Some(&(deadline, _)) = self.deadlines.last() {
131                if Instant::now() > deadline {
132                    self.deadlines.pop();
133                    let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
134                    match self.handle_error(err) {
135                        Ok(None) => continue,
136                        Ok(Some(val)) => return Ok(val),
137                        Err(e) => return Err(e),
138                    }
139                }
140            }
141
142            let frame = match self.frames.last_mut() {
143                Some(f) => f,
144                None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
145            };
146
147            if frame.ip >= frame.chunk.code.len() {
148                let val = self.stack.pop().unwrap_or(VmValue::Nil);
149                self.release_sync_guards_for_frame(self.frames.len());
150                let popped_frame = self.frames.pop().unwrap();
151                if let Some(ref dir) = popped_frame.saved_source_dir {
152                    crate::stdlib::set_thread_source_dir(dir);
153                }
154
155                if self.frames.is_empty() {
156                    return Ok(val);
157                } else {
158                    self.iterators.truncate(popped_frame.saved_iterator_depth);
159                    self.env = popped_frame.saved_env;
160                    self.stack.truncate(popped_frame.stack_base);
161                    self.stack.push(val);
162                    continue;
163                }
164            }
165
166            let op = frame.chunk.code[frame.ip];
167            frame.ip += 1;
168
169            match self.execute_op(op).await {
170                Ok(Some(val)) => return Ok(val),
171                Ok(None) => continue,
172                Err(VmError::Return(val)) => {
173                    if let Some(popped_frame) = self.frames.pop() {
174                        self.release_sync_guards_for_frame(self.frames.len() + 1);
175                        if let Some(ref dir) = popped_frame.saved_source_dir {
176                            crate::stdlib::set_thread_source_dir(dir);
177                        }
178                        let current_depth = self.frames.len();
179                        self.exception_handlers
180                            .retain(|h| h.frame_depth <= current_depth);
181
182                        if self.frames.is_empty() {
183                            return Ok(val);
184                        }
185                        self.iterators.truncate(popped_frame.saved_iterator_depth);
186                        self.env = popped_frame.saved_env;
187                        self.stack.truncate(popped_frame.stack_base);
188                        self.stack.push(val);
189                    } else {
190                        return Ok(val);
191                    }
192                }
193                Err(e) => {
194                    // Capture stack trace before error handling unwinds frames.
195                    if self.error_stack_trace.is_empty() {
196                        self.error_stack_trace = self.capture_stack_trace();
197                    }
198                    match self.handle_error(e) {
199                        Ok(None) => {
200                            self.error_stack_trace.clear();
201                            continue;
202                        }
203                        Ok(Some(val)) => return Ok(val),
204                        Err(e) => return Err(self.enrich_error_with_line(e)),
205                    }
206                }
207            }
208        }
209    }
210
211    pub(crate) async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
212        if let Some(&(deadline, _)) = self.deadlines.last() {
213            if Instant::now() > deadline {
214                self.deadlines.pop();
215                let err = VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")));
216                match self.handle_error(err) {
217                    Ok(None) => return Ok(None),
218                    Ok(Some(val)) => return Ok(Some((val, false))),
219                    Err(e) => return Err(e),
220                }
221            }
222        }
223
224        let frame = match self.frames.last_mut() {
225            Some(f) => f,
226            None => {
227                let val = self.stack.pop().unwrap_or(VmValue::Nil);
228                return Ok(Some((val, false)));
229            }
230        };
231
232        if frame.ip >= frame.chunk.code.len() {
233            let val = self.stack.pop().unwrap_or(VmValue::Nil);
234            self.release_sync_guards_for_frame(self.frames.len());
235            let popped_frame = self.frames.pop().unwrap();
236            if self.frames.is_empty() {
237                return Ok(Some((val, false)));
238            } else {
239                self.iterators.truncate(popped_frame.saved_iterator_depth);
240                self.env = popped_frame.saved_env;
241                self.stack.truncate(popped_frame.stack_base);
242                self.stack.push(val);
243                return Ok(None);
244            }
245        }
246
247        let op = frame.chunk.code[frame.ip];
248        frame.ip += 1;
249
250        match self.execute_op(op).await {
251            Ok(Some(val)) => Ok(Some((val, false))),
252            Ok(None) => Ok(None),
253            Err(VmError::Return(val)) => {
254                if let Some(popped_frame) = self.frames.pop() {
255                    self.release_sync_guards_for_frame(self.frames.len() + 1);
256                    if let Some(ref dir) = popped_frame.saved_source_dir {
257                        crate::stdlib::set_thread_source_dir(dir);
258                    }
259                    let current_depth = self.frames.len();
260                    self.exception_handlers
261                        .retain(|h| h.frame_depth <= current_depth);
262                    if self.frames.is_empty() {
263                        return Ok(Some((val, false)));
264                    }
265                    self.iterators.truncate(popped_frame.saved_iterator_depth);
266                    self.env = popped_frame.saved_env;
267                    self.stack.truncate(popped_frame.stack_base);
268                    self.stack.push(val);
269                    Ok(None)
270                } else {
271                    Ok(Some((val, false)))
272                }
273            }
274            Err(e) => {
275                if self.error_stack_trace.is_empty() {
276                    self.error_stack_trace = self.capture_stack_trace();
277                }
278                match self.handle_error(e) {
279                    Ok(None) => {
280                        self.error_stack_trace.clear();
281                        Ok(None)
282                    }
283                    Ok(Some(val)) => Ok(Some((val, false))),
284                    Err(e) => Err(self.enrich_error_with_line(e)),
285                }
286            }
287        }
288    }
289
290    /// Capture the current call stack as (fn_name, line, col, source_file) tuples.
291    pub(crate) fn capture_stack_trace(&self) -> Vec<(String, usize, usize, Option<String>)> {
292        self.frames
293            .iter()
294            .map(|f| {
295                let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
296                let line = f.chunk.lines.get(idx).copied().unwrap_or(0) as usize;
297                let col = f.chunk.columns.get(idx).copied().unwrap_or(0) as usize;
298                (f.fn_name.clone(), line, col, f.chunk.source_file.clone())
299            })
300            .collect()
301    }
302
303    /// Enrich a VmError with source line information from the captured stack
304    /// trace. Appends ` (line N)` to error variants whose messages don't
305    /// already carry location context.
306    pub(crate) fn enrich_error_with_line(&self, error: VmError) -> VmError {
307        // Determine the line from the captured stack trace (innermost frame).
308        let line = self
309            .error_stack_trace
310            .last()
311            .map(|(_, l, _, _)| *l)
312            .unwrap_or_else(|| self.current_line());
313        if line == 0 {
314            return error;
315        }
316        let suffix = format!(" (line {line})");
317        match error {
318            VmError::Runtime(msg) => VmError::Runtime(format!("{msg}{suffix}")),
319            VmError::TypeError(msg) => VmError::TypeError(format!("{msg}{suffix}")),
320            VmError::DivisionByZero => VmError::Runtime(format!("Division by zero{suffix}")),
321            VmError::UndefinedVariable(name) => {
322                VmError::Runtime(format!("Undefined variable: {name}{suffix}"))
323            }
324            VmError::UndefinedBuiltin(name) => {
325                VmError::Runtime(format!("Undefined builtin: {name}{suffix}"))
326            }
327            VmError::ImmutableAssignment(name) => VmError::Runtime(format!(
328                "Cannot assign to immutable binding: {name}{suffix}"
329            )),
330            VmError::StackOverflow => {
331                VmError::Runtime(format!("Stack overflow: too many nested calls{suffix}"))
332            }
333            // Leave these untouched:
334            // - Thrown: user-thrown errors should not be silently modified
335            // - CategorizedError: structured errors for agent orchestration
336            // - Return: control flow, not a real error
337            // - StackUnderflow / InvalidInstruction: internal VM bugs
338            other => other,
339        }
340    }
341}