1use std::rc::Rc;
2use std::time::{Duration, Instant};
3
4use crate::chunk::{Chunk, ChunkRef};
5use crate::value::{ModuleFunctionRegistry, VmError, VmValue};
6
7use super::{CallFrame, LocalSlot, Vm};
8
9const CANCEL_GRACE_INSTRUCTIONS: usize = 1024;
10const CANCEL_GRACE_ASYNC_OP: Duration = Duration::from_millis(250);
11
12impl Vm {
13 pub async fn execute(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
15 let span_id = crate::tracing::span_start(crate::tracing::SpanKind::Pipeline, "main".into());
16 let result = self.run_chunk(chunk).await;
17 crate::tracing::span_end(span_id);
18 result
19 }
20
21 pub(crate) fn handle_error(&mut self, error: VmError) -> Result<Option<VmValue>, VmError> {
23 let thrown_value = match &error {
24 VmError::Thrown(v) => v.clone(),
25 other => VmValue::String(Rc::from(other.to_string())),
26 };
27
28 if let Some(handler) = self.exception_handlers.pop() {
29 if !handler.error_type.is_empty() {
30 let matches = match &thrown_value {
32 VmValue::EnumVariant { enum_name, .. } => {
33 enum_name.as_ref() == handler.error_type
34 }
35 _ => false,
36 };
37 if !matches {
38 return self.handle_error(error);
39 }
40 }
41
42 self.release_sync_guards_after_unwind(handler.frame_depth, handler.env_scope_depth);
43
44 while self.frames.len() > handler.frame_depth {
45 if let Some(frame) = self.frames.pop() {
46 if let Some(ref dir) = frame.saved_source_dir {
47 crate::stdlib::set_thread_source_dir(dir);
48 }
49 self.iterators.truncate(frame.saved_iterator_depth);
50 self.env = frame.saved_env;
51 }
52 }
53
54 while self
56 .deadlines
57 .last()
58 .is_some_and(|d| d.1 > handler.frame_depth)
59 {
60 self.deadlines.pop();
61 }
62
63 self.env.truncate_scopes(handler.env_scope_depth);
64
65 self.stack.truncate(handler.stack_depth);
66 self.stack.push(thrown_value);
67
68 if let Some(frame) = self.frames.last_mut() {
69 frame.ip = handler.catch_ip;
70 }
71
72 Ok(None)
73 } else {
74 Err(error)
75 }
76 }
77
78 pub(crate) async fn run_chunk(&mut self, chunk: &Chunk) -> Result<VmValue, VmError> {
79 self.run_chunk_entry(chunk, 0, None, None, None, None).await
80 }
81
82 pub(crate) async fn run_chunk_entry(
83 &mut self,
84 chunk: &Chunk,
85 argc: usize,
86 saved_source_dir: Option<std::path::PathBuf>,
87 module_functions: Option<ModuleFunctionRegistry>,
88 module_state: Option<crate::value::ModuleState>,
89 local_slots: Option<Vec<LocalSlot>>,
90 ) -> Result<VmValue, VmError> {
91 self.run_chunk_ref(
92 Rc::new(chunk.clone()),
93 argc,
94 saved_source_dir,
95 module_functions,
96 module_state,
97 local_slots,
98 )
99 .await
100 }
101
102 pub(crate) async fn run_chunk_ref(
103 &mut self,
104 chunk: ChunkRef,
105 argc: usize,
106 saved_source_dir: Option<std::path::PathBuf>,
107 module_functions: Option<ModuleFunctionRegistry>,
108 module_state: Option<crate::value::ModuleState>,
109 local_slots: Option<Vec<LocalSlot>>,
110 ) -> Result<VmValue, VmError> {
111 let initial_env = self.env.clone();
112 let local_slots = local_slots.unwrap_or_else(|| Self::fresh_local_slots(&chunk));
113 let initial_local_slots = local_slots.clone();
114 self.frames.push(CallFrame {
115 chunk,
116 ip: 0,
117 stack_base: self.stack.len(),
118 saved_env: self.env.clone(),
119 initial_env: Some(initial_env),
120 initial_local_slots: Some(initial_local_slots),
121 saved_iterator_depth: self.iterators.len(),
122 fn_name: String::new(),
123 argc,
124 saved_source_dir,
125 module_functions,
126 module_state,
127 local_slots,
128 local_scope_base: self.env.scope_depth().saturating_sub(1),
129 local_scope_depth: 0,
130 });
131
132 loop {
133 if let Some(err) = self.pending_scope_interrupt() {
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 let frame = match self.frames.last_mut() {
142 Some(f) => f,
143 None => return Ok(self.stack.pop().unwrap_or(VmValue::Nil)),
144 };
145
146 if frame.ip >= frame.chunk.code.len() {
147 let val = self.stack.pop().unwrap_or(VmValue::Nil);
148 self.release_sync_guards_for_frame(self.frames.len());
149 let popped_frame = self.frames.pop().unwrap();
150 if let Some(ref dir) = popped_frame.saved_source_dir {
151 crate::stdlib::set_thread_source_dir(dir);
152 }
153
154 if self.frames.is_empty() {
155 return Ok(val);
156 } else {
157 self.iterators.truncate(popped_frame.saved_iterator_depth);
158 self.env = popped_frame.saved_env;
159 self.stack.truncate(popped_frame.stack_base);
160 self.stack.push(val);
161 continue;
162 }
163 }
164
165 let op = frame.chunk.code[frame.ip];
166 frame.ip += 1;
167
168 match self.execute_op_with_scope_interrupts(op).await {
169 Ok(Some(val)) => return Ok(val),
170 Ok(None) => continue,
171 Err(VmError::Return(val)) => {
172 if let Some(popped_frame) = self.frames.pop() {
173 self.release_sync_guards_for_frame(self.frames.len() + 1);
174 if let Some(ref dir) = popped_frame.saved_source_dir {
175 crate::stdlib::set_thread_source_dir(dir);
176 }
177 let current_depth = self.frames.len();
178 self.exception_handlers
179 .retain(|h| h.frame_depth <= current_depth);
180
181 if self.frames.is_empty() {
182 return Ok(val);
183 }
184 self.iterators.truncate(popped_frame.saved_iterator_depth);
185 self.env = popped_frame.saved_env;
186 self.stack.truncate(popped_frame.stack_base);
187 self.stack.push(val);
188 } else {
189 return Ok(val);
190 }
191 }
192 Err(e) => {
193 if self.error_stack_trace.is_empty() {
195 self.error_stack_trace = self.capture_stack_trace();
196 }
197 match self.handle_error(e) {
198 Ok(None) => {
199 self.error_stack_trace.clear();
200 continue;
201 }
202 Ok(Some(val)) => return Ok(val),
203 Err(e) => return Err(self.enrich_error_with_line(e)),
204 }
205 }
206 }
207 }
208 }
209
210 pub(crate) async fn execute_one_cycle(&mut self) -> Result<Option<(VmValue, bool)>, VmError> {
211 if let Some(err) = self.pending_scope_interrupt() {
212 match self.handle_error(err) {
213 Ok(None) => return Ok(None),
214 Ok(Some(val)) => return Ok(Some((val, false))),
215 Err(e) => return Err(e),
216 }
217 }
218
219 let frame = match self.frames.last_mut() {
220 Some(f) => f,
221 None => {
222 let val = self.stack.pop().unwrap_or(VmValue::Nil);
223 return Ok(Some((val, false)));
224 }
225 };
226
227 if frame.ip >= frame.chunk.code.len() {
228 let val = self.stack.pop().unwrap_or(VmValue::Nil);
229 self.release_sync_guards_for_frame(self.frames.len());
230 let popped_frame = self.frames.pop().unwrap();
231 if self.frames.is_empty() {
232 return Ok(Some((val, false)));
233 } else {
234 self.iterators.truncate(popped_frame.saved_iterator_depth);
235 self.env = popped_frame.saved_env;
236 self.stack.truncate(popped_frame.stack_base);
237 self.stack.push(val);
238 return Ok(None);
239 }
240 }
241
242 let op = frame.chunk.code[frame.ip];
243 frame.ip += 1;
244
245 match self.execute_op_with_scope_interrupts(op).await {
246 Ok(Some(val)) => Ok(Some((val, false))),
247 Ok(None) => Ok(None),
248 Err(VmError::Return(val)) => {
249 if let Some(popped_frame) = self.frames.pop() {
250 self.release_sync_guards_for_frame(self.frames.len() + 1);
251 if let Some(ref dir) = popped_frame.saved_source_dir {
252 crate::stdlib::set_thread_source_dir(dir);
253 }
254 let current_depth = self.frames.len();
255 self.exception_handlers
256 .retain(|h| h.frame_depth <= current_depth);
257 if self.frames.is_empty() {
258 return Ok(Some((val, false)));
259 }
260 self.iterators.truncate(popped_frame.saved_iterator_depth);
261 self.env = popped_frame.saved_env;
262 self.stack.truncate(popped_frame.stack_base);
263 self.stack.push(val);
264 Ok(None)
265 } else {
266 Ok(Some((val, false)))
267 }
268 }
269 Err(e) => {
270 if self.error_stack_trace.is_empty() {
271 self.error_stack_trace = self.capture_stack_trace();
272 }
273 match self.handle_error(e) {
274 Ok(None) => {
275 self.error_stack_trace.clear();
276 Ok(None)
277 }
278 Ok(Some(val)) => Ok(Some((val, false))),
279 Err(e) => Err(self.enrich_error_with_line(e)),
280 }
281 }
282 }
283 }
284
285 fn pending_scope_interrupt(&mut self) -> Option<VmError> {
286 if self.is_cancel_requested() {
287 match self.cancel_grace_instructions_remaining.as_mut() {
288 Some(0) => {
289 self.cancel_spawned_tasks();
290 return Some(Self::cancelled_error());
291 }
292 Some(remaining) => *remaining -= 1,
293 None => self.cancel_grace_instructions_remaining = Some(CANCEL_GRACE_INSTRUCTIONS),
294 }
295 } else {
296 self.cancel_grace_instructions_remaining = None;
297 }
298 if let Some(&(deadline, _)) = self.deadlines.last() {
299 if Instant::now() >= deadline {
300 self.deadlines.pop();
301 return Some(Self::deadline_exceeded_error());
302 }
303 }
304 None
305 }
306
307 async fn execute_op_with_scope_interrupts(
308 &mut self,
309 op: u8,
310 ) -> Result<Option<VmValue>, VmError> {
311 enum ScopeInterruptResult {
312 Op(Result<Option<VmValue>, VmError>),
313 Deadline,
314 CancelTimedOut,
315 }
316
317 let deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
318 let cancel_token = self.cancel_token.clone();
319
320 if deadline.is_none() && cancel_token.is_none() {
321 return self.execute_op(op).await;
322 }
323
324 let has_deadline = deadline.is_some();
325 let cancel_requested_at_start = cancel_token
326 .as_ref()
327 .is_some_and(|token| token.load(std::sync::atomic::Ordering::SeqCst));
328 let has_cancel = cancel_token.is_some() && !cancel_requested_at_start;
329 let deadline_sleep = async move {
330 if let Some(deadline) = deadline {
331 tokio::time::sleep_until(tokio::time::Instant::from_std(deadline)).await;
332 } else {
333 std::future::pending::<()>().await;
334 }
335 };
336 let cancel_sleep = async move {
337 if let Some(token) = cancel_token {
338 while !token.load(std::sync::atomic::Ordering::SeqCst) {
339 tokio::time::sleep(Duration::from_millis(10)).await;
340 }
341 } else {
342 std::future::pending::<()>().await;
343 }
344 };
345
346 let result = {
347 let op_future = self.execute_op(op);
348 tokio::pin!(op_future);
349 tokio::select! {
350 result = &mut op_future => ScopeInterruptResult::Op(result),
351 _ = deadline_sleep, if has_deadline => ScopeInterruptResult::Deadline,
352 _ = cancel_sleep, if has_cancel => {
353 let grace = tokio::time::sleep(CANCEL_GRACE_ASYNC_OP);
354 tokio::pin!(grace);
355 tokio::select! {
356 result = &mut op_future => ScopeInterruptResult::Op(result),
357 _ = &mut grace => ScopeInterruptResult::CancelTimedOut,
358 }
359 }
360 }
361 };
362
363 match result {
364 ScopeInterruptResult::Op(result) => result,
365 ScopeInterruptResult::Deadline => {
366 self.deadlines.pop();
367 self.cancel_spawned_tasks();
368 Err(Self::deadline_exceeded_error())
369 }
370 ScopeInterruptResult::CancelTimedOut => {
371 self.cancel_spawned_tasks();
372 Err(Self::cancelled_error())
373 }
374 }
375 }
376
377 pub(crate) fn deadline_exceeded_error() -> VmError {
378 VmError::Thrown(VmValue::String(Rc::from("Deadline exceeded")))
379 }
380
381 pub(crate) fn cancelled_error() -> VmError {
382 VmError::Thrown(VmValue::String(Rc::from(
383 "kind:cancelled:VM cancelled by host",
384 )))
385 }
386
387 pub(crate) fn capture_stack_trace(&self) -> Vec<(String, usize, usize, Option<String>)> {
389 self.frames
390 .iter()
391 .map(|f| {
392 let idx = if f.ip > 0 { f.ip - 1 } else { 0 };
393 let line = f.chunk.lines.get(idx).copied().unwrap_or(0) as usize;
394 let col = f.chunk.columns.get(idx).copied().unwrap_or(0) as usize;
395 (f.fn_name.clone(), line, col, f.chunk.source_file.clone())
396 })
397 .collect()
398 }
399
400 pub(crate) fn enrich_error_with_line(&self, error: VmError) -> VmError {
404 let line = self
406 .error_stack_trace
407 .last()
408 .map(|(_, l, _, _)| *l)
409 .unwrap_or_else(|| self.current_line());
410 if line == 0 {
411 return error;
412 }
413 let suffix = format!(" (line {line})");
414 match error {
415 VmError::Runtime(msg) => VmError::Runtime(format!("{msg}{suffix}")),
416 VmError::TypeError(msg) => VmError::TypeError(format!("{msg}{suffix}")),
417 VmError::DivisionByZero => VmError::Runtime(format!("Division by zero{suffix}")),
418 VmError::UndefinedVariable(name) => {
419 VmError::Runtime(format!("Undefined variable: {name}{suffix}"))
420 }
421 VmError::UndefinedBuiltin(name) => {
422 VmError::Runtime(format!("Undefined builtin: {name}{suffix}"))
423 }
424 VmError::ImmutableAssignment(name) => VmError::Runtime(format!(
425 "Cannot assign to immutable binding: {name}{suffix}"
426 )),
427 VmError::StackOverflow => {
428 VmError::Runtime(format!("Stack overflow: too many nested calls{suffix}"))
429 }
430 other => other,
436 }
437 }
438}