1use std::cell::RefCell;
17use std::collections::{HashMap, VecDeque};
18use std::rc::{Rc, Weak};
19
20use super::error::VmError;
21use super::fiber::{VmFiber, VmFiberState};
22use super::frame::Frame;
23use super::opcode::Instruction;
24use super::program::{FunctionPrototype, Program};
25use super::slot::{VmClosure, VmMultiArity, VmSlot};
26use crate::core::{
27 call_value, native_fiber_function, with_namespace_registry, Cont, Promise, PromiseState, Step,
28 Value,
29};
30use crate::task::promise::settle_result;
31
32#[path = "machine/async_runtime.rs"]
33mod async_runtime;
34#[path = "machine/constants.rs"]
35mod constants;
36#[path = "machine/coroutine_runtime.rs"]
37mod coroutine_runtime;
38use constants::constant_string;
39#[path = "machine/dispatch.rs"]
40mod dispatch;
41use dispatch::Dispatch;
42#[path = "machine/globals.rs"]
43mod globals;
44use async_runtime::{async_result, async_result_from_outcome};
45#[cfg(feature = "bytecode-instrumentation")]
46#[path = "machine/instrumentation.rs"]
47pub mod instrumentation;
48#[cfg(feature = "bytecode-observation")]
49#[path = "machine/observation.rs"]
50pub mod observation;
51
52pub enum VmOutcome {
56 Returned(Value),
57 Failed(VmError),
58 Suspended(Promise),
59 Yielded(Value),
60}
61
62pub struct Machine {
64 program: Rc<Program>,
65 function: usize,
66 frame: Frame,
67 stack: Vec<VmSlot>,
68 scratch: Vec<Value>,
69 calls: Vec<SavedFrame>,
70 free_locals: Vec<Vec<VmSlot>>,
71 free_args: Vec<Vec<VmSlot>>,
72 vm_globals: HashMap<usize, VmSlot>,
73 next_closure_identity: u64,
74 scheduler: Weak<RefCell<AsyncScheduler>>,
75 scheduler_owner: Option<Rc<RefCell<AsyncScheduler>>>,
76 ip: usize,
77 #[cfg(feature = "tracing-jit")]
78 jit: crate::jit::runtime::JitRuntime,
79 #[cfg(feature = "tracing-jit")]
80 jit_path: Vec<(usize, u32)>,
81 #[cfg(feature = "tracing-jit")]
82 jit_suppressed_range: Option<(usize, u32, u32)>,
83 #[cfg(feature = "tracing-jit")]
84 jit_loop_entries: HashMap<(usize, u32), Vec<crate::jit::TraceValue>>,
85 #[cfg(feature = "tracing-jit")]
86 jit_status_function: usize,
87 #[cfg(feature = "tracing-jit")]
88 jit_function_disabled: bool,
89}
90
91#[cfg(feature = "tracing-jit")]
92struct CachedJit {
93 program: Weak<Program>,
94 runtime: crate::jit::runtime::JitRuntime,
95}
96
97#[cfg(feature = "tracing-jit")]
98thread_local! {
99 static PROGRAM_JITS: RefCell<HashMap<usize, CachedJit>> = RefCell::new(HashMap::new());
100}
101
102#[cfg(feature = "tracing-jit")]
103const MAX_PROGRAM_JITS: usize = 128;
104
105#[cfg(feature = "tracing-jit")]
106fn program_key(program: &Rc<Program>) -> usize {
107 Rc::as_ptr(program) as usize
108}
109
110#[cfg(feature = "tracing-jit")]
111fn take_program_jit(program: &Rc<Program>) -> crate::jit::runtime::JitRuntime {
112 PROGRAM_JITS.with(|cache| {
113 cache
114 .borrow_mut()
115 .remove(&program_key(program))
116 .filter(|cached| {
117 cached
118 .program
119 .upgrade()
120 .is_some_and(|owner| Rc::ptr_eq(&owner, program))
121 })
122 .map(|cached| cached.runtime)
123 .unwrap_or_default()
124 })
125}
126
127#[cfg(feature = "tracing-jit")]
128fn store_program_jit(program: &Rc<Program>, runtime: crate::jit::runtime::JitRuntime) {
129 PROGRAM_JITS.with(|cache| {
130 let mut cache = cache.borrow_mut();
131 cache.retain(|_, cached| cached.program.strong_count() > 0);
132 if cache.len() >= MAX_PROGRAM_JITS && !cache.contains_key(&program_key(program)) {
133 cache.clear();
137 }
138 cache.insert(
139 program_key(program),
140 CachedJit {
141 program: Rc::downgrade(program),
142 runtime,
143 },
144 );
145 });
146}
147
148struct SavedFrame {
149 function: usize,
150 frame: Frame,
151 call_ip: usize,
152}
153
154struct AsyncChild {
155 machine: Machine,
156 result: crate::task::promise::WeakPromise,
157 pending: Promise,
158}
159
160#[derive(Default)]
161struct AsyncScheduler {
162 next_id: u64,
163 children: HashMap<u64, AsyncChild>,
164 ready: VecDeque<(u64, PromiseState)>,
165 polling: bool,
166}
167
168impl Machine {
169 #[cfg(feature = "tracing-jit")]
170 pub(super) fn attach_cached_jit(&mut self) {
171 self.jit = take_program_jit(&self.program);
172 }
173
174 #[cfg(feature = "tracing-jit")]
175 pub(super) fn detach_cached_jit(&mut self) {
176 store_program_jit(&self.program.clone(), std::mem::take(&mut self.jit));
177 }
178
179 pub fn entry(program: Rc<Program>) -> Machine {
181 let index = usize::from(program.entry);
182 let local_count = usize::from(program.functions[index].local_count);
183 let max_stack = usize::from(program.functions[index].max_stack);
184 let scheduler = Rc::new(RefCell::new(AsyncScheduler::default()));
185 Machine {
186 program,
187 function: index,
188 frame: Frame::entry(local_count),
189 stack: Vec::with_capacity(max_stack),
190 scratch: Vec::new(),
191 calls: Vec::new(),
192 free_locals: Vec::new(),
193 free_args: Vec::new(),
194 vm_globals: HashMap::new(),
195 next_closure_identity: 0,
196 scheduler: Rc::downgrade(&scheduler),
197 scheduler_owner: Some(scheduler),
198 ip: 0,
199 #[cfg(feature = "tracing-jit")]
200 jit: crate::jit::runtime::JitRuntime::default(),
201 #[cfg(feature = "tracing-jit")]
202 jit_path: Vec::new(),
203 #[cfg(feature = "tracing-jit")]
204 jit_suppressed_range: None,
205 #[cfg(feature = "tracing-jit")]
206 jit_loop_entries: HashMap::new(),
207 #[cfg(feature = "tracing-jit")]
208 jit_status_function: usize::MAX,
209 #[cfg(feature = "tracing-jit")]
210 jit_function_disabled: false,
211 }
212 }
213
214 pub fn call(
217 program: Rc<Program>,
218 prototype: u16,
219 args: Vec<Value>,
220 captures: Vec<Value>,
221 ) -> Machine {
222 Machine::call_slots(
223 program,
224 prototype,
225 args.into_iter().map(VmSlot::from).collect(),
226 captures.into_iter().map(VmSlot::from).collect(),
227 )
228 }
229
230 fn call_slots(
231 program: Rc<Program>,
232 prototype: u16,
233 args: Vec<VmSlot>,
234 captures: Vec<VmSlot>,
235 ) -> Machine {
236 let scheduler = Rc::new(RefCell::new(AsyncScheduler::default()));
237 Self::call_slots_with_scheduler(
238 program,
239 prototype,
240 args,
241 captures,
242 Rc::downgrade(&scheduler),
243 Some(scheduler),
244 )
245 }
246
247 fn call_slots_with_scheduler(
248 program: Rc<Program>,
249 prototype: u16,
250 mut args: Vec<VmSlot>,
251 captures: Vec<VmSlot>,
252 scheduler: Weak<RefCell<AsyncScheduler>>,
253 scheduler_owner: Option<Rc<RefCell<AsyncScheduler>>>,
254 ) -> Machine {
255 let index = usize::from(prototype);
256 let proto = &program.functions[index];
257 let mut arity = usize::from(proto.arity);
258 if proto.variadic {
259 let fixed = arity.min(args.len());
264 let rest = args
265 .split_off(fixed)
266 .into_iter()
267 .map(|value| Machine::into_value(program.clone(), value))
268 .collect();
269 args.push(Value::List(rest).into());
270 arity = fixed + 1;
271 }
272 Machine {
273 frame: Frame::call(usize::from(proto.local_count), arity, args, captures, 0),
274 stack: Vec::with_capacity(usize::from(proto.max_stack)),
275 program,
276 function: index,
277 scratch: Vec::new(),
278 calls: Vec::new(),
279 free_locals: Vec::new(),
280 free_args: Vec::new(),
281 vm_globals: HashMap::new(),
282 next_closure_identity: 0,
283 scheduler,
284 scheduler_owner,
285 ip: 0,
286 #[cfg(feature = "tracing-jit")]
287 jit: crate::jit::runtime::JitRuntime::default(),
288 #[cfg(feature = "tracing-jit")]
289 jit_path: Vec::new(),
290 #[cfg(feature = "tracing-jit")]
291 jit_suppressed_range: None,
292 #[cfg(feature = "tracing-jit")]
293 jit_loop_entries: HashMap::new(),
294 #[cfg(feature = "tracing-jit")]
295 jit_status_function: usize::MAX,
296 #[cfg(feature = "tracing-jit")]
297 jit_function_disabled: false,
298 }
299 }
300
301 fn into_value(program: Rc<Program>, slot: VmSlot) -> Value {
302 match slot {
303 VmSlot::Number(value) => Value::Number(value),
304 VmSlot::Bool(value) => Value::Bool(value),
305 VmSlot::Nil => Value::Nil,
306 VmSlot::Value(value) => Rc::try_unwrap(value).unwrap_or_else(|value| (*value).clone()),
307 VmSlot::InlineClosure { prototype, .. } => Self::closure_value(
308 program,
309 Rc::new(VmClosure {
310 prototype,
311 captures: Vec::new(),
312 }),
313 ),
314 VmSlot::Closure(closure) => Self::closure_value(program, closure),
315 VmSlot::MultiArity(dispatch) => {
316 let functions = dispatch
317 .clauses
318 .iter()
319 .cloned()
320 .map(
321 |closure| match Self::closure_value(program.clone(), closure) {
322 Value::Function(function) => function,
323 _ => unreachable!(),
324 },
325 )
326 .collect();
327 crate::core::arity_dispatcher(&dispatch.name, functions, false)
328 }
329 }
330 }
331
332 fn callable_key(value: &Value) -> Option<usize> {
333 match value {
334 Value::Function(function) => Some(Rc::as_ptr(function) as usize),
335 _ => None,
336 }
337 }
338
339 fn remember_vm_global(&mut self, value: &Value, slot: VmSlot) {
340 if let Some(key) = Self::callable_key(value) {
341 self.vm_globals.insert(key, slot);
342 }
343 }
344
345 fn enter_callable(
346 &mut self,
347 program: &Rc<Program>,
348 callee: VmSlot,
349 mut args: Vec<VmSlot>,
350 ) -> Result<(), String> {
351 match callee {
352 VmSlot::InlineClosure { prototype, .. } => {
353 self.check_arity(program, prototype, args.len())?;
354 self.enter_or_spawn(program, prototype, args, Vec::new());
355 Ok(())
356 }
357 VmSlot::Closure(closure) => {
358 self.check_arity(program, closure.prototype, args.len())?;
359 self.enter_or_spawn(program, closure.prototype, args, closure.captures.clone());
360 Ok(())
361 }
362 VmSlot::MultiArity(dispatch) => {
363 let closure = dispatch
364 .clauses
365 .iter()
366 .find(|closure| {
367 let proto = &program.functions[usize::from(closure.prototype)];
368 (!proto.variadic && usize::from(proto.arity) == args.len())
369 || (proto.variadic && args.len() >= usize::from(proto.arity))
370 })
371 .cloned()
372 .ok_or_else(|| format!("{} has no arity {}", dispatch.name, args.len()))?;
373 self.enter_or_spawn(program, closure.prototype, args, closure.captures.clone());
374 Ok(())
375 }
376 value => {
377 let callee = Self::into_value(program.clone(), value);
378 let runtime_args = args
379 .drain(..)
380 .map(|value| Self::into_value(program.clone(), value))
381 .collect();
382 self.free_args.push(args);
383 let value = if let Some(position) = program.functions[self.function]
384 .source_map
385 .position(self.ip)
386 {
387 crate::core::with_exception_site(
388 crate::core::ExceptionSite {
389 namespace: program.namespace.clone(),
390 resource: None,
391 line: position.line,
392 column: position.column,
393 },
394 || call_value(callee, runtime_args),
395 )?
396 } else {
397 call_value(callee, runtime_args)?
398 };
399 self.stack.push(value.into());
400 self.ip += 1;
401 Ok(())
402 }
403 }
404 }
405
406 fn enter_or_spawn(
407 &mut self,
408 program: &Rc<Program>,
409 prototype: u16,
410 args: Vec<VmSlot>,
411 captures: Vec<VmSlot>,
412 ) {
413 if program.functions[usize::from(prototype)].async_function {
414 let mut child = Machine::call_slots(program.clone(), prototype, args, captures);
415 child.vm_globals = self.vm_globals.clone();
416 child.next_closure_identity = self.next_closure_identity;
417 self.stack
418 .push(Value::Promise(self.spawn_async(child)).into());
419 self.ip += 1;
420 } else {
421 self.enter_prototype(program, prototype, args, captures);
422 }
423 }
424
425 fn check_arity(&self, program: &Program, prototype: u16, argc: usize) -> Result<(), String> {
426 let proto = &program.functions[usize::from(prototype)];
427 let arity = usize::from(proto.arity);
428 if (!proto.variadic && argc != arity) || (proto.variadic && argc < arity) {
429 let expectation = if proto.variadic {
430 format!("at least {arity}")
431 } else {
432 arity.to_string()
433 };
434 return Err(format!("function expects {expectation} arguments"));
435 }
436 Ok(())
437 }
438
439 fn enter_prototype(
440 &mut self,
441 program: &Program,
442 prototype: u16,
443 mut args: Vec<VmSlot>,
444 captures: Vec<VmSlot>,
445 ) {
446 let proto = &program.functions[usize::from(prototype)];
447 let mut frame_arity = usize::from(proto.arity);
448 if proto.variadic {
449 let fixed = frame_arity.min(args.len());
450 let rest = args
451 .split_off(fixed)
452 .into_iter()
453 .map(|value| Self::into_value(self.program.clone(), value))
454 .collect();
455 args.push(Value::List(rest).into());
456 frame_arity = fixed + 1;
457 }
458 let locals = self.free_locals.pop().unwrap_or_default();
459 let frame = Frame::call_reusing(
460 locals,
461 usize::from(proto.local_count),
462 frame_arity,
463 &mut args,
464 captures,
465 self.stack.len(),
466 );
467 self.free_args.push(args);
468 let caller = std::mem::replace(&mut self.frame, frame);
469 self.calls.push(SavedFrame {
470 function: self.function,
471 frame: caller,
472 call_ip: self.ip,
473 });
474 self.function = usize::from(prototype);
475 self.ip = 0;
476 }
477
478 fn enter_static_direct(&mut self, program: &Program, prototype: u16, argc: u8) {
481 let proto = &program.functions[usize::from(prototype)];
482 debug_assert_eq!(proto.capture_count, 0);
483 debug_assert!(!proto.async_function);
484 debug_assert!(!proto.variadic);
485 debug_assert_eq!(usize::from(proto.arity), usize::from(argc));
486 let locals = self.free_locals.pop().unwrap_or_default();
487 let frame = Frame::call_static_reusing(
488 locals,
489 usize::from(proto.local_count),
490 &mut self.stack,
491 usize::from(argc),
492 );
493 let caller = std::mem::replace(&mut self.frame, frame);
494 self.calls.push(SavedFrame {
495 function: self.function,
496 frame: caller,
497 call_ip: self.ip,
498 });
499 self.function = usize::from(prototype);
500 self.ip = 0;
501 }
502
503 pub fn run(&mut self) -> VmOutcome {
505 let program = self.program.clone();
506 loop {
509 let Some(function) = program.functions.get(self.function) else {
510 return VmOutcome::Failed(VmError::new("function index out of range", 0, None));
511 };
512 let Some(instruction) = function.code.get(self.ip) else {
513 return VmOutcome::Failed(self.error(function, "instruction pointer out of range"));
514 };
515 #[cfg(feature = "tracing-jit")]
516 {
517 if !self.jit_function_disabled || self.jit_status_function != self.function {
521 if self.jit_status_function != self.function {
522 self.jit_function_disabled = self
523 .jit
524 .function_is_fully_disabled(&program, self.function as u16);
525 self.jit_status_function = self.function;
526 }
527 if !self.jit_function_disabled {
528 let instruction = self.ip as u32;
529 let suppressed = self.jit_suppressed_range.is_some_and(
530 |(function, header, backedge)| {
531 function == self.function
532 && instruction >= header
533 && instruction <= backedge
534 },
535 );
536 if !suppressed {
537 self.jit_suppressed_range = None;
538 self.jit_path.push((self.function, instruction));
539 }
540 }
541 }
542 }
543 match self.dispatch(&program, function, instruction) {
544 Dispatch::Next(ip) | Dispatch::Unwound(ip) => {
545 #[cfg(feature = "tracing-jit")]
546 let mut next_ip = ip;
547 #[cfg(not(feature = "tracing-jit"))]
548 let next_ip = ip;
549 #[cfg(feature = "tracing-jit")]
550 if !self.jit_function_disabled && ip <= self.ip {
551 let header = ip as u32;
552 if self.jit.is_disabled(self.function as u16, header) {
553 self.jit_suppressed_range =
554 Some((self.function, header, self.ip as u32));
555 } else {
556 let (mut locals, writable) = self.frame.trace_locals();
557 let recording_locals = self
558 .jit_loop_entries
559 .get(&(self.function, header))
560 .cloned()
561 .unwrap_or_else(|| locals.clone());
562 let path_start = self
563 .jit_path
564 .iter()
565 .rposition(|entry| *entry == (self.function, header));
566 let path = path_start.map_or_else(Vec::new, |start| {
567 self.jit_path[start..]
568 .iter()
569 .map(|(_, instruction)| *instruction)
570 .collect()
571 });
572 if let Some(snapshot) = self.jit.backedge(
573 &program,
574 self.function as u16,
575 self.ip as u32,
576 header,
577 &path,
578 &recording_locals,
579 &mut locals,
580 ) {
581 self.frame.apply_trace_locals(&snapshot.locals, &writable);
582 locals = snapshot.locals;
583 next_ip = snapshot.instruction as usize;
584 }
585 self.jit_loop_entries
586 .insert((self.function, header), locals);
587 if self.jit.is_disabled(self.function as u16, header) {
588 self.jit_suppressed_range =
589 Some((self.function, header, self.ip as u32));
590 self.jit_status_function = usize::MAX;
591 }
592 }
593 self.jit_path.clear();
594 }
595 self.ip = next_ip;
596 }
597 Dispatch::Call { callee, args } => {
598 #[cfg(feature = "tracing-jit")]
599 {
600 self.jit_path.clear();
601 self.jit_loop_entries.clear();
602 }
603 if let Err(message) = self.enter_callable(&program, callee, args) {
604 match self.raise(function, message) {
605 Ok(target) => self.ip = target,
606 Err(error) => return VmOutcome::Failed(error),
607 }
608 }
609 }
610 Dispatch::CallStatic {
611 prototype,
612 args,
613 captures,
614 } => {
615 #[cfg(feature = "tracing-jit")]
616 {
617 self.jit_path.clear();
618 self.jit_loop_entries.clear();
619 }
620 self.enter_or_spawn(&program, prototype, args, captures)
621 }
622 Dispatch::CallStaticDirect { prototype, argc } => {
623 #[cfg(feature = "tracing-jit")]
624 {
625 self.jit_path.clear();
626 self.jit_loop_entries.clear();
627 }
628 self.enter_static_direct(&program, prototype, argc)
629 }
630 Dispatch::Returned(value) => {
631 #[cfg(feature = "tracing-jit")]
632 {
633 self.jit_path.clear();
634 self.jit_loop_entries.clear();
635 }
636 self.stack.truncate(self.frame.base());
637 if let Some(caller) = self.calls.pop() {
638 self.function = caller.function;
639 let completed = std::mem::replace(&mut self.frame, caller.frame);
640 self.free_locals.push(completed.into_locals());
641 self.ip = caller.call_ip + 1;
642 self.stack.push(value);
643 } else {
644 return VmOutcome::Returned(Self::into_value(program.clone(), value));
645 }
646 }
647 Dispatch::Suspended(promise) => return VmOutcome::Suspended(promise),
648 Dispatch::Yielded(value) => return VmOutcome::Yielded(value),
649 Dispatch::Failed(error) => return VmOutcome::Failed(error),
650 }
651 }
652 }
653
654 fn collect_call(&mut self, argc: u8) -> Result<(VmSlot, Vec<VmSlot>), String> {
656 let argc = usize::from(argc);
657 if self.stack.len() < argc + 1 {
658 return Err("stack underflow".to_string());
659 }
660 let mut args = self.free_args.pop().unwrap_or_default();
661 args.extend(self.stack.drain(self.stack.len() - argc..));
662 let callee = self.stack.pop().expect("callee checked above");
663 Ok((callee, args))
664 }
665
666 fn collect_call_static(
669 &mut self,
670 program: &Program,
671 function: &FunctionPrototype,
672 prototype: u16,
673 argc: u8,
674 ) -> Result<(u16, Vec<VmSlot>, Vec<VmSlot>), String> {
675 let argc = usize::from(argc);
676 if self.stack.len() < argc {
677 return Err("stack underflow".to_string());
678 }
679 let Some(proto) = program.functions.get(usize::from(prototype)) else {
680 return Err(format!("callstatic target {prototype} out of range"));
681 };
682 let capture_count = usize::from(proto.capture_count);
683 let mut args = self.free_args.pop().unwrap_or_default();
684 args.extend(self.stack.drain(self.stack.len() - argc..));
685 let capture_base = usize::from(function.arity) + usize::from(function.variadic);
686 let Some(captures) = self.frame.slot_range(capture_base, capture_count) else {
687 return Err("capture slots out of range".to_string());
688 };
689 Ok((prototype, args, captures))
690 }
691
692 #[inline(never)]
699 fn exec_closure(
700 &mut self,
701 program: &Rc<Program>,
702 prototype: u16,
703 captures: u8,
704 ) -> Result<(), String> {
705 let captures = usize::from(captures);
706 if self.stack.len() < captures {
707 return Err("stack underflow".to_string());
708 }
709 let Some(_proto) = program.functions.get(usize::from(prototype)) else {
710 return Err(format!("closure prototype {prototype} out of range"));
711 };
712 if captures == 0 {
713 let identity = self.next_closure_identity;
714 self.next_closure_identity = self.next_closure_identity.wrapping_add(1);
715 self.stack.push(VmSlot::InlineClosure {
716 prototype,
717 identity,
718 });
719 } else {
720 let captured = self.stack.split_off(self.stack.len() - captures);
721 self.stack.push(VmSlot::Closure(Rc::new(VmClosure {
722 prototype,
723 captures: captured,
724 })));
725 }
726 Ok(())
727 }
728
729 #[cold]
734 #[inline(never)]
735 fn raise(
736 &mut self,
737 _function: &FunctionPrototype,
738 message: impl Into<String>,
739 ) -> Result<usize, VmError> {
740 let message = message.into();
741 loop {
742 let function = &self.program.functions[self.function];
743 let error_ip = self.ip;
744 for entry in function.handlers.iter().rev() {
745 let (start, end) = (entry.start as usize, entry.end as usize);
746 if error_ip < start || error_ip >= end {
747 continue;
748 }
749 let depth = self.frame.base() + usize::from(entry.depth);
750 if self.stack.len() < depth {
751 return Err(self.error(function, "handler stack depth out of range"));
752 }
753 for catch in &entry.catches {
754 if crate::core::catch_matches(&message, &catch.class) {
755 self.stack.truncate(depth);
756 let value = crate::core::caught_error(&message);
757 if !self.frame.store(catch.binding, value.into()) {
758 return Err(self.error(function, "catch binding slot out of range"));
759 }
760 return Ok(catch.target as usize);
761 }
762 }
763 if let Some(finally) = entry.finally {
764 let (Some(value_slot), Some(flag_slot)) =
765 (entry.pending_value, entry.pending_error)
766 else {
767 return Err(self.error(function, "handler pending slots missing"));
768 };
769 self.stack.truncate(depth);
770 if !self
771 .frame
772 .store(value_slot, crate::core::caught_error(&message).into())
773 || !self.frame.store(flag_slot, Value::Bool(true).into())
774 {
775 return Err(self.error(function, "pending slot out of range"));
776 }
777 return Ok(finally as usize);
778 }
779 }
780
781 let Some(caller) = self.calls.pop() else {
782 return Err(VmError::new(
783 message,
784 error_ip as u32,
785 function.source_map.position(error_ip),
786 ));
787 };
788 self.stack.truncate(self.frame.base());
789 self.function = caller.function;
790 let completed = std::mem::replace(&mut self.frame, caller.frame);
791 self.free_locals.push(completed.into_locals());
792 self.ip = caller.call_ip;
793 }
794 }
795
796 fn error(&self, function: &FunctionPrototype, message: impl Into<String>) -> VmError {
797 VmError::new(
798 message,
799 self.ip as u32,
800 function.source_map.position(self.ip),
801 )
802 }
803}
804
805impl Machine {
806 pub fn resume(&mut self, state: PromiseState) -> VmOutcome {
810 let Some(function) = self.program.functions.get(self.function).cloned() else {
811 return VmOutcome::Failed(VmError::new("function index out of range", 0, None));
812 };
813 let protocol_deref = match function.code.get(self.ip) {
814 Some(Instruction::ProtocolCall { target, argc }) if *argc == 1 => {
815 constant_string(&self.program, *target)
816 .is_some_and(|name| name == "std.protocol.ideref.IDeref/deref")
817 }
818 _ => false,
819 };
820 if !protocol_deref && !matches!(function.code.get(self.ip), Some(Instruction::Await)) {
821 return VmOutcome::Failed(
822 self.error(&function, "VM is not suspended at await or deref"),
823 );
824 }
825 match state {
826 PromiseState::Pending => {
827 let promise = match self.stack.last().and_then(VmSlot::runtime_value) {
828 Some(Value::Promise(promise)) => promise,
829 _ => {
830 return VmOutcome::Failed(self.error(
831 &function,
832 if protocol_deref {
833 "deref expects a promise"
834 } else {
835 "await expects a promise"
836 },
837 ))
838 }
839 };
840 return VmOutcome::Suspended(promise);
841 }
842 PromiseState::Fulfilled(value) => {
843 self.stack.pop();
844 self.stack.push(value.into());
845 self.ip += 1;
846 }
847 PromiseState::Rejected(error) => {
848 self.stack.pop();
849 let message = if protocol_deref {
850 crate::core::promise_rejection_error(error)
851 } else {
852 error.message()
853 };
854 match self.raise(&function, message) {
855 Ok(target) => self.ip = target,
856 Err(error) => return VmOutcome::Failed(error),
857 }
858 }
859 }
860 self.run()
861 }
862
863 pub fn resume_yield(&mut self, value: Value) -> VmOutcome {
864 let Some(function) = self.program.functions.get(self.function).cloned() else {
865 return VmOutcome::Failed(VmError::new("function index out of range", 0, None));
866 };
867 if !matches!(function.code.get(self.ip), Some(Instruction::Yield)) {
868 return VmOutcome::Failed(self.error(&function, "VM is not suspended at yield"));
869 }
870 self.stack.push(value.into());
871 self.ip += 1;
872 self.run()
873 }
874}
875
876fn run_entry(program: Rc<Program>) -> Result<Value, VmError> {
877 let mut machine = Machine::entry(program.clone());
878 #[cfg(feature = "tracing-jit")]
879 {
880 machine.jit = take_program_jit(&program);
881 }
882 let outcome = machine.run();
883 #[cfg(feature = "tracing-jit")]
884 store_program_jit(&program, machine.jit);
885 match outcome {
886 VmOutcome::Returned(value) => Ok(value),
887 VmOutcome::Failed(error) => Err(error),
888 VmOutcome::Suspended(_) => Err(VmError::new(
889 "VM fiber suspended on an unresolved promise",
890 0,
891 None,
892 )),
893 VmOutcome::Yielded(_) => Err(VmError::new(
894 "coroutine/yield used outside of a coroutine",
895 0,
896 None,
897 )),
898 }
899}
900
901#[cfg(feature = "tracing-jit")]
902fn cached_jit_runtime<R>(
903 program: &Rc<Program>,
904 access: impl FnOnce(&crate::jit::runtime::JitRuntime) -> R,
905) -> Option<R> {
906 PROGRAM_JITS.with(|cache| {
907 cache
908 .borrow()
909 .get(&program_key(program))
910 .and_then(|cached| {
911 cached
912 .program
913 .upgrade()
914 .map(|owner| (owner, &cached.runtime))
915 })
916 .filter(|(owner, _)| Rc::ptr_eq(owner, program))
917 .map(|(_, runtime)| access(runtime))
918 })
919}
920
921#[cfg(all(test, feature = "tracing-jit"))]
922pub(crate) fn cached_trace_count(program: &Rc<Program>) -> usize {
923 cached_jit_runtime(program, crate::jit::runtime::JitRuntime::compiled_count).unwrap_or(0)
924}
925
926#[cfg(all(test, feature = "tracing-jit"))]
927pub(crate) fn active_compiled_trace_count() -> usize {
928 PROGRAM_JITS.with(|cache| {
929 cache
930 .borrow()
931 .values()
932 .filter(|cached| cached.program.strong_count() > 0)
933 .map(|cached| cached.runtime.compiled_count())
934 .sum()
935 })
936}
937
938#[cfg(all(test, feature = "tracing-jit"))]
939pub(crate) fn active_jit_telemetry() -> Vec<crate::jit::JitTelemetry> {
940 PROGRAM_JITS.with(|cache| {
941 cache
942 .borrow()
943 .values()
944 .filter(|cached| cached.program.strong_count() > 0)
945 .map(|cached| cached.runtime.telemetry())
946 .collect()
947 })
948}
949
950#[cfg(feature = "tracing-jit")]
951pub(crate) fn cached_jit_telemetry(program: &Rc<Program>) -> crate::jit::JitTelemetry {
952 cached_jit_runtime(program, crate::jit::runtime::JitRuntime::telemetry).unwrap_or_default()
953}
954
955pub fn execute_program(program: Rc<Program>) -> Result<Value, VmError> {
965 if crate::core::namespace_registry().is_ok() {
966 return run_entry(program);
967 }
968 let registry = crate::kernel::NamespaceRegistry::new("user");
969 with_namespace_registry(®istry, || run_entry(program))
970}
971
972pub fn execute_program_with_globals(
976 program: Rc<Program>,
977 globals: &crate::kernel::NamespaceRegistry<Value>,
978) -> Result<Value, VmError> {
979 with_namespace_registry(globals, || run_entry(program))
980}