1use crate::{
8 Context, JsError, JsExpect, JsNativeError, JsObject, JsResult, JsString, JsValue, Module,
9 builtins::promise::{PromiseCapability, ResolvingFunctions},
10 environments::EnvironmentStack,
11 error::RuntimeLimitError,
12 object::JsFunction,
13 realm::Realm,
14 script::Script,
15 vm::opcode::{OPCODE_HANDLERS, OPCODE_HANDLERS_BUDGET},
16 vm::shadow_stack::ErrorStack,
17};
18use boa_gc::{Finalize, Gc, Trace, custom_trace};
19use shadow_stack::ShadowStack;
20use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task};
21
22#[cfg(feature = "trace")]
23use crate::sys::time::Instant;
24
25#[allow(unused_imports)]
26pub(crate) use opcode::{Instruction, InstructionIterator, Opcode};
27
28pub(crate) use {
29 call_frame::CallFrameFlags,
30 code_block::{
31 CodeBlockFlags, Constant, Handler, create_function_object, create_function_object_fast,
32 },
33 completion_record::CompletionRecord,
34 inline_cache::InlineCache,
35};
36
37pub use execution_budget::ExecutionScope;
38pub use runtime_limits::RuntimeLimits;
39pub use {
40 call_frame::{CallFrame, GeneratorResumeKind},
41 code_block::CodeBlock,
42 source_info::{NativeSourceInfo, SourcePath},
43};
44
45pub(crate) use code_block::GlobalFunctionBinding;
46
47mod call_frame;
48mod code_block;
49mod completion_record;
50mod execution_budget;
51mod inline_cache;
52#[cfg(feature = "jit")]
53pub(crate) mod jit;
54mod runtime_limits;
55
56#[cfg(debug_assertions)]
57pub(crate) mod verify;
58
59pub(crate) mod opcode;
60pub(crate) mod shadow_stack;
61pub(crate) mod source_info;
62
63#[cfg(feature = "flowgraph")]
64pub mod flowgraph;
65
66#[cfg(test)]
67mod tests;
68
69#[derive(Debug)]
71pub struct Vm {
72 pub(crate) frames: Vec<CallFrame>,
77
78 pub(crate) stack: Stack,
79
80 pub(crate) return_value: JsValue,
81
82 pub(crate) pending_exception: Option<JsError>,
91 pub(crate) runtime_limits: RuntimeLimits,
92 pub(crate) interrupt_flag: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
94 pub(crate) execution_budget: std::sync::Arc<execution_budget::ExecutionBudget>,
95
96 pub(crate) native_active_function: Option<JsObject>,
99
100 pub(crate) native_constructor_receiver: Option<JsValue>,
103
104 pub(crate) host_call_depth: usize,
110
111 pub(crate) shadow_stack: ShadowStack,
112
113 #[cfg(feature = "trace")]
114 pub(crate) trace: bool,
115 #[cfg(feature = "trace")]
116 pub(crate) current_frame: Option<*const CallFrame>,
117}
118
119#[derive(Clone, Debug, Trace, Finalize)]
138pub(crate) struct Stack {
139 stack: Vec<JsValue>,
140}
141
142impl Stack {
143 fn new(capacity: usize) -> Self {
145 Self {
146 stack: Vec::with_capacity(capacity),
147 }
148 }
149
150 pub(crate) fn get_register(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
152 self.stack.get(frame.rp as usize + index)
153 }
154
155 pub(crate) fn set_register(&mut self, frame: &CallFrame, index: usize, value: JsValue) {
157 let old = std::mem::replace(&mut self.stack[frame.rp as usize + index], value);
158 unsafe { old.release_outside_collection() };
160 }
161
162 #[inline]
164 pub(crate) fn truncate_to_frame(&mut self, frame: &CallFrame) {
165 for value in self.stack.drain(frame.frame_pointer()..) {
166 unsafe { value.release_outside_collection() };
168 }
169 }
170
171 pub(crate) fn split_off_frame(&mut self, frame: &CallFrame) -> Self {
173 let frame_pointer = frame.frame_pointer();
174 Self {
175 stack: self.stack.split_off(frame_pointer),
176 }
177 }
178
179 pub(crate) fn get_this(&self, frame: &CallFrame) -> JsValue {
181 self.stack[frame.this_index()].clone()
182 }
183
184 pub(crate) fn set_this(&mut self, frame: &CallFrame, this: JsValue) {
186 let old = std::mem::replace(&mut self.stack[frame.this_index()], this);
187 unsafe { old.release_outside_collection() };
189 }
190
191 pub(crate) fn get_function(&self, frame: &CallFrame) -> Option<JsObject> {
193 self.stack[frame.function_index()].as_object()
194 }
195
196 pub(crate) fn get_arguments(&self, frame: &CallFrame) -> &[JsValue] {
198 &self.stack[frame.arguments_range()]
199 }
200
201 #[inline]
203 pub(crate) fn get_argument(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
204 if index >= frame.argument_count as usize {
205 return None;
206 }
207 let start = frame.fp as usize + CallFrame::FUNCTION_PROLOGUE as usize;
208 self.stack.get(start + index)
209 }
210
211 pub(crate) fn pop_rest_arguments(&mut self, frame: &CallFrame) -> Option<Vec<JsValue>> {
213 let argument_count = frame.argument_count as usize;
214 let param_count = frame.code_block().parameter_length as usize;
215 if argument_count < param_count {
216 return None;
217 }
218 let args_start = frame.fp as usize + CallFrame::FUNCTION_PROLOGUE as usize;
219 let args_end = args_start + argument_count;
220 let rest_count = argument_count - param_count + 1;
221
222 Some(
223 self.stack
224 .drain((args_end - rest_count)..args_end)
225 .collect(),
226 )
227 }
228
229 pub(crate) fn push<T>(&mut self, value: T)
231 where
232 T: Into<JsValue>,
233 {
234 self.stack.push(value.into());
235 }
236
237 #[track_caller]
243 pub(crate) fn pop(&mut self) -> JsValue {
244 self.stack.pop().expect("stack was empty")
245 }
246
247 pub(crate) fn calling_convention_pop_arguments(
250 &mut self,
251 argument_count: usize,
252 ) -> Vec<JsValue> {
253 let index = self.stack.len() - argument_count;
254 self.stack.split_off(index)
255 }
256
257 pub(crate) fn calling_convention_pop_arguments_into(
260 &mut self,
261 argument_count: usize,
262 out: &mut impl Extend<JsValue>,
263 ) {
264 let index = self.stack.len() - argument_count;
265 out.extend(self.stack.drain(index..));
266 }
267
268 #[cfg(feature = "jit")]
271 pub(crate) fn calling_convention_numbers(
272 &self,
273 argument_count: usize,
274 out: &mut [f64],
275 ) -> bool {
276 let start = self.stack.len() - argument_count;
277 for (slot, value) in out.iter_mut().zip(&self.stack[start..]) {
278 let Some(number) = value.as_number() else {
279 return false;
280 };
281 *slot = number;
282 }
283 true
284 }
285
286 #[cfg(feature = "jit")]
289 pub(crate) fn calling_convention_replace_call(
290 &mut self,
291 argument_count: usize,
292 result: JsValue,
293 ) {
294 let len = self.stack.len() - argument_count - 2;
295 self.stack.truncate(len);
296 self.stack.push(result);
297 }
298
299 #[inline]
301 pub(crate) fn calling_convention_push_call(
302 &mut self,
303 this: JsValue,
304 function: JsValue,
305 values: &[JsValue],
306 ) {
307 self.stack
308 .reserve(CallFrame::FUNCTION_PROLOGUE as usize + values.len());
309 self.stack.push(this);
310 self.stack.push(function);
311 self.stack.extend_from_slice(values);
312 }
313
314 #[inline]
317 pub(crate) fn calling_convention_push_arguments(&mut self, values: &[JsValue]) {
318 self.stack.extend_from_slice(values);
319 }
320
321 #[track_caller]
323 pub(crate) fn calling_convention_get_function(&self, argument_count: usize) -> &JsValue {
324 let index = self.stack.len() - 1 - argument_count;
325 self.stack
326 .get(index)
327 .expect("invalid calling convention function index")
328 }
329
330 #[track_caller]
332 pub(crate) fn calling_convention_set_function(
333 &mut self,
334 argument_count: usize,
335 function: JsValue,
336 ) {
337 let index = self.stack.len() - 1 - argument_count;
338 self.stack[index] = function;
339 }
340
341 #[track_caller]
343 pub(crate) fn calling_convention_set_this(&mut self, argument_count: usize, function: JsValue) {
344 let index = self.stack.len() - 2 - argument_count;
345 self.stack[index] = function;
346 }
347
348 pub(crate) fn calling_convention_insert_arguments(
351 &mut self,
352 existing_argument_count: usize,
353 arguments: &[JsValue],
354 ) {
355 let index = self.stack.len() - existing_argument_count;
356 self.stack.splice(index..index, arguments.iter().cloned());
357 }
358
359 #[cfg(feature = "trace")]
360 const MAX_VALUE_LEN: usize = 18;
361 #[cfg(feature = "trace")]
362 const MAX_STACK_WIDTH: usize = 68;
363
364 #[cfg(feature = "trace")]
365 fn raw_value(value: &JsValue) -> String {
366 match value {
367 v if v.is_callable() => "func".to_string(),
368 v if v.is_object() => "obj".to_string(),
369 v if v.is_undefined() => "und".to_string(),
370 v if v.is_null() => "null".to_string(),
371 v => v.display().to_string(),
372 }
373 }
374
375 #[cfg(feature = "trace")]
376 fn truncate_display(val: &str) -> String {
377 if val.len() <= Self::MAX_VALUE_LEN {
378 return val.to_string();
379 }
380 let mut end = Self::MAX_VALUE_LEN - 2;
381 while !val.is_char_boundary(end) && end > 0 {
382 end -= 1;
383 }
384 format!("{}..", &val[..end])
385 }
386
387 #[cfg(feature = "trace")]
388 fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String {
389 let total = self.stack.len();
390 if total == 0 {
391 return "[ <empty> ]".to_string();
392 }
393
394 let mut groups: Vec<(String, usize, Option<usize>)> = Vec::new();
395 let mut force_truncate = false;
396
397 for (idx, v) in self.stack.iter().enumerate().rev() {
399 let is_frame = frame.frame_pointer() == idx;
400 let raw = Self::raw_value(v);
401
402 if !is_frame
403 && let Some(last) = groups.last_mut()
404 && last.0 == raw
405 && last.2.is_none()
406 {
407 last.1 += 1;
408 } else {
409 let marker = if is_frame { Some(frame_count) } else { None };
410 groups.push((raw, 1, marker));
411
412 if groups.len() > Self::MAX_STACK_WIDTH / 2 {
415 force_truncate = true;
416 break;
417 }
418 }
419 }
420
421 let mut string = String::from("[ ");
422 let mut truncated = force_truncate;
423 let suffix = format!(".. ({total} total) ]");
424
425 for (i, (val, count, marker)) in groups.iter().enumerate() {
426 let display_val = Self::truncate_display(val);
427 let part = if *count > 1 {
428 format!("{display_val} (x{count})")
429 } else {
430 display_val
431 };
432
433 let separator = if let Some(fc) = marker {
434 format!(" |{fc}|")
435 } else if i + 1 < groups.len() {
436 ",".to_string()
437 } else {
438 String::new()
439 };
440
441 let addition = format!("{part}{separator} ");
442 if string.len() + addition.len() + suffix.len() > Self::MAX_STACK_WIDTH {
443 truncated = true;
444 break;
445 }
446 string.push_str(&addition);
447 }
448
449 if truncated {
450 string.push_str(&suffix);
451 } else {
452 string.push(']');
453 }
454 string
455 }
456}
457
458impl Extend<JsValue> for Stack {
459 #[inline]
460 fn extend<T: IntoIterator<Item = JsValue>>(&mut self, values: T) {
461 self.stack.extend(values);
462 }
463}
464
465#[derive(Debug, Clone, Finalize)]
467pub enum ActiveRunnable {
468 Script(Script),
470 Module(Module),
472}
473
474unsafe impl Trace for ActiveRunnable {
475 custom_trace!(this, mark, {
476 match this {
477 Self::Script(script) => mark(script),
478 Self::Module(module) => mark(module),
479 }
480 });
481}
482
483impl ActiveRunnable {
484 #[must_use]
486 pub fn path(&self) -> Option<&Path> {
487 match self {
488 Self::Script(script) => script.path(),
489 Self::Module(module) => module.path(),
490 }
491 }
492}
493
494impl Vm {
495 pub(crate) fn new(realm: Realm) -> Self {
497 let mut frames = Vec::with_capacity(16);
498 frames.push(CallFrame::new(
499 Gc::new(CodeBlock::new(JsString::default(), 0, true)),
500 None,
501 EnvironmentStack::new(),
502 realm,
503 ));
504 Self {
505 frames,
506 stack: Stack::new(1024),
507 return_value: JsValue::undefined(),
508 pending_exception: None,
509 runtime_limits: RuntimeLimits::default(),
510 interrupt_flag: None,
511 execution_budget: std::sync::Arc::new(execution_budget::ExecutionBudget::default()),
512 native_active_function: None,
513 native_constructor_receiver: None,
514 host_call_depth: 0,
515 shadow_stack: ShadowStack::default(),
516 #[cfg(feature = "trace")]
517 trace: false,
518 #[cfg(feature = "trace")]
519 current_frame: None,
520 }
521 }
522
523 #[track_caller]
524 #[inline]
525 pub(crate) fn set_register(&mut self, index: usize, value: JsValue) {
526 let rp = self.frame().rp as usize;
527 debug_assert!(
528 rp + index < self.stack.stack.len(),
529 "register index out of bounds: rp {rp}, index {index}, stack len {}",
530 self.stack.stack.len()
531 );
532 let old =
536 unsafe { std::mem::replace(self.stack.stack.get_unchecked_mut(rp + index), value) };
537 unsafe { old.release_outside_collection() };
539 }
540
541 #[track_caller]
542 #[inline]
543 pub(crate) fn get_register(&self, index: usize) -> &JsValue {
544 let rp = self.frame().rp as usize;
545 debug_assert!(
546 rp + index < self.stack.stack.len(),
547 "register index out of bounds: rp {rp}, index {index}, stack len {}",
548 self.stack.stack.len()
549 );
550 unsafe { self.stack.stack.get_unchecked(rp + index) }
554 }
555
556 #[track_caller]
561 #[inline]
562 pub(crate) fn take_register(&mut self, index: usize) -> JsValue {
563 let rp = self.frame().rp as usize;
564 debug_assert!(
565 rp + index < self.stack.stack.len(),
566 "register index out of bounds: rp {rp}, index {index}, stack len {}",
567 self.stack.stack.len()
568 );
569 unsafe { std::mem::take(self.stack.stack.get_unchecked_mut(rp + index)) }
573 }
574
575 #[track_caller]
577 pub(crate) fn set_promise_capability(
578 &mut self,
579 promise_capability: PromiseCapability,
580 ) -> JsResult<()> {
581 #[cfg(debug_assertions)]
582 {
583 if !self.frame().code_block().is_async() {
584 return Err(crate::error::PanicError::new(
585 "only async functions and modules with a top-level-await \
586 can have a promise capability",
587 )
588 .into());
589 }
590 }
591
592 let rp = self.frame().rp as usize;
593 self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX] =
594 promise_capability.promise.into();
595 self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX] =
596 promise_capability.functions.resolve.into();
597 self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX] =
598 promise_capability.functions.reject.into();
599
600 Ok(())
601 }
602
603 #[track_caller]
605 pub(crate) fn get_promise_capability(&self) -> JsResult<PromiseCapability> {
606 #[cfg(debug_assertions)]
607 if !self.frame().code_block().is_async() {
608 return Err(crate::error::PanicError::new(
609 "cannot get promise capability from non-async code",
610 )
611 .into());
612 }
613
614 let rp = self.frame().rp as usize;
615 let promise = self
616 .stack
617 .stack
618 .get(rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX)
619 .and_then(JsValue::as_object)
620 .js_expect("registers must have a promise capability")?;
621 let resolve = self
622 .stack
623 .stack
624 .get(rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX)
625 .and_then(JsValue::as_object)
626 .and_then(JsFunction::from_object)
627 .js_expect("registers must have a resolve function")?;
628 let reject = self
629 .stack
630 .stack
631 .get(rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX)
632 .and_then(JsValue::as_object)
633 .and_then(JsFunction::from_object)
634 .js_expect("registers must have a reject function")?;
635
636 Ok(PromiseCapability {
637 promise,
638 functions: ResolvingFunctions { resolve, reject },
639 })
640 }
641
642 #[track_caller]
644 pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
645 if !self.frame().code_block().is_async_generator() {
646 return None;
647 }
648
649 let rp = self.frame().rp as usize;
650 self.stack
651 .stack
652 .get(rp + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX)
653 .expect("registers must have an async generator object")
654 .as_object()
655 }
656
657 #[track_caller]
663 #[inline]
664 pub(crate) fn frame(&self) -> &CallFrame {
665 unsafe { self.frames.last().unwrap_unchecked() }
667 }
668
669 #[track_caller]
675 #[inline]
676 pub(crate) fn frame_mut(&mut self) -> &mut CallFrame {
677 unsafe { self.frames.last_mut().unwrap_unchecked() }
679 }
680
681 pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
682 self.set_return_value(JsValue::undefined());
684
685 if !frame.registers_already_pushed() {
689 let current_stack_length = self.stack.stack.len() as u32;
690 frame.fp = current_stack_length - frame.argument_count - CallFrame::FUNCTION_PROLOGUE;
691
692 let register_count = frame.code_block.register_count as usize;
693 frame.rp = self.stack.stack.len() as u32;
694 self.stack.stack.resize(
695 self.stack.stack.len() + register_count,
696 JsValue::undefined(),
697 );
698 }
699
700 if frame.active_runnable.is_none() {
703 let current = self.frame();
704 frame.active_runnable.clone_from(¤t.active_runnable);
705 }
706
707 let current_pc = self.frame().pc;
708 self.shadow_stack
709 .push_bytecode(current_pc, frame.code_block().source_info.clone());
710
711 self.frames.push(frame);
712 }
713
714 pub(crate) fn push_frame_with_stack(
715 &mut self,
716 frame: CallFrame,
717 this: JsValue,
718 function: JsValue,
719 ) {
720 self.stack.push(this);
721 self.stack.push(function);
722
723 self.push_frame(frame);
724 }
725
726 pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
727 if self.frames.len() <= 1 {
729 return None;
730 }
731 self.shadow_stack.pop();
732 self.frames.pop()
733 }
734
735 #[inline]
739 pub(crate) fn handle_exception_at(&mut self, pc: u32) -> bool {
740 let frame = self.frame_mut();
741 let Some((_, handler)) = frame.code_block().find_handler(pc) else {
742 return false;
743 };
744
745 let catch_address = handler.handler();
746 let environment_sp = frame.env_fp + handler.environment_count;
747
748 frame.pc = u32::from(catch_address);
750
751 self.frame_mut()
752 .environments
753 .truncate(environment_sp as usize);
754
755 true
756 }
757
758 pub(crate) fn get_return_value(&self) -> JsValue {
759 self.return_value.clone()
760 }
761
762 #[inline]
763 pub(crate) fn set_return_value(&mut self, value: JsValue) {
764 let old = std::mem::replace(&mut self.return_value, value);
765 unsafe { old.release_outside_collection() };
767 }
768
769 pub(crate) fn take_return_value(&mut self) -> JsValue {
770 std::mem::take(&mut self.return_value)
771 }
772}
773
774#[allow(clippy::print_stdout)]
775#[cfg(feature = "trace")]
776impl Context {
777 const COLUMN_WIDTH: usize = 26;
778 const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
779 const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
780 const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
781 const NUMBER_OF_COLUMNS: usize = 4;
782
783 pub(crate) fn trace_call_frame(&self) {
784 let frame = self.vm.frame();
785 let msg = if self.vm.frames.is_empty() {
786 " VM Start ".to_string()
787 } else {
788 format!(
789 " Call Frame '{}'{} ",
790 frame.code_block().name().to_std_string_escaped(),
791 if frame.code_block().name().is_empty() {
792 format!(" [anon#{}]", frame.code_block().debug_id)
793 } else {
794 String::new()
795 }
796 )
797 };
798
799 if !frame.code_block.traced.get() {
801 println!("{}", frame.code_block);
802 frame.code_block.traced.set(true);
803 }
804 println!(
805 "{msg:-^width$}",
806 width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
807 );
808 println!(
809 "{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
810 "Time",
811 "Opcode",
812 "Operands",
813 TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
814 OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
815 OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
816 );
817 }
818
819 fn trace_execute_instruction<F>(
820 &mut self,
821 f: F,
822 opcode: Opcode,
823 ) -> ControlFlow<CompletionRecord>
824 where
825 F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
826 {
827 if self.vm.current_frame != Some(self.vm.frame()) {
828 println!();
829 self.trace_call_frame();
830 self.vm.current_frame = Some(self.vm.frame());
831 }
832 let frame = self.vm.frame();
833 let (instruction, _) = frame
834 .code_block
835 .bytecode
836 .next_instruction(frame.pc as usize);
837 let operands = self
838 .vm
839 .frame()
840 .code_block()
841 .instruction_operands(&instruction);
842
843 let instant = Instant::now();
844 let result = self.execute_instruction(f, opcode);
845 let duration = instant.elapsed();
846
847 let stack = self
848 .vm
849 .stack
850 .display_trace(self.vm.frame(), self.vm.frames.len() - 1);
851
852 println!(
853 "{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
854 format!("{}μs", duration.as_micros()),
855 opcode.as_str(),
856 TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
857 OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
858 OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
859 );
860
861 result
862 }
863}
864
865impl Context {
866 pub fn set_interrupt_flag(&mut self, flag: std::sync::Arc<std::sync::atomic::AtomicBool>) {
871 self.vm.interrupt_flag = Some(flag);
872 }
873
874 pub fn set_execution_time_limit(&mut self, duration: Option<std::time::Duration>) {
880 self.vm.execution_budget.set_duration(duration);
881 }
882
883 pub fn enter_execution_time_limit(&self) -> ExecutionScope {
888 self.vm.execution_budget.begin()
889 }
890
891 pub(crate) fn execution_time_limit_exceeded(&self) -> bool {
892 self.vm.execution_budget.expired()
893 }
894
895 fn execute_instruction<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
896 where
897 F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
898 {
899 f(self, opcode)
900 }
901
902 fn execute_one<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
903 where
904 F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
905 {
906 if self
907 .vm
908 .interrupt_flag
909 .as_ref()
910 .is_some_and(|flag| flag.load(std::sync::atomic::Ordering::Acquire))
911 || self.vm.execution_budget.should_interrupt()
912 {
913 return self.handle_error(RuntimeLimitError::Interrupted.into());
914 }
915
916 #[cfg(feature = "fuzz")]
917 {
918 use crate::error::EngineError;
919 if self.instructions_remaining == 0 {
920 return ControlFlow::Break(CompletionRecord::Throw(
921 EngineError::NoInstructionsRemain.into(),
922 ));
923 }
924 self.instructions_remaining -= 1;
925 }
926
927 #[cfg(feature = "trace")]
928 if self.vm.trace || self.vm.frame().code_block.traceable() {
929 self.trace_execute_instruction(f, opcode)
930 } else {
931 self.execute_instruction(f, opcode)
932 }
933
934 #[cfg(not(feature = "trace"))]
935 self.execute_instruction(f, opcode)
936 }
937
938 fn handle_error(&mut self, mut err: JsError) -> ControlFlow<CompletionRecord> {
939 if err.backtrace.is_none() {
943 err.backtrace = Some(
944 self.vm
945 .shadow_stack
946 .take(self.vm.runtime_limits.backtrace_limit(), self.vm.frame().pc),
947 );
948 }
949
950 if !err.is_catchable() {
953 let mut frame = None;
954 let mut env_fp = self.vm.frame().environments.len();
955 loop {
956 if self.vm.frame().exit_early() {
957 break;
958 }
959
960 env_fp = self.vm.frame().env_fp as usize;
961
962 let Some(f) = self.vm.pop_frame() else {
963 break;
964 };
965 frame = Some(f);
966 }
967 self.vm.frame_mut().environments.truncate(env_fp);
968 if let Some(frame) = frame {
969 self.vm.stack.truncate_to_frame(&frame);
970 }
971 return ControlFlow::Break(CompletionRecord::Throw(err));
972 }
973
974 if let Some(native) = err.as_native_mut()
975 && let ErrorStack::Position(position) = &mut native.stack.0
976 {
977 let backtrace = self.vm.shadow_stack.take_and_push(
978 self.vm.runtime_limits.backtrace_limit(),
979 self.vm.frame().pc,
980 position.clone(),
981 );
982 native.stack.0 = ErrorStack::Backtrace(backtrace);
983 }
984
985 let pc = self.vm.frame().pc.saturating_sub(1);
987 if self.vm.handle_exception_at(pc) {
988 self.vm.pending_exception = Some(err);
989 return ControlFlow::Continue(());
990 }
991
992 let err = err.inject_realm(self.realm().clone());
994
995 self.vm.pending_exception = Some(err);
996 self.handle_throw()
997 }
998
999 #[inline]
1000 fn handle_return(&mut self) -> ControlFlow<CompletionRecord> {
1001 let exit_early = self.vm.frame().exit_early();
1002 let frame = self.vm.frames.last().expect("frame must exist");
1003 self.vm.stack.truncate_to_frame(frame);
1004
1005 let result = self.vm.take_return_value();
1006 if exit_early {
1007 return ControlFlow::Break(CompletionRecord::Return(result));
1008 }
1009
1010 self.vm.stack.push(result);
1011 self.vm.pop_frame().expect("frame must exist");
1012 ControlFlow::Continue(())
1013 }
1014
1015 fn handle_yield(&mut self) -> ControlFlow<CompletionRecord> {
1016 let result = self.vm.take_return_value();
1017 if self.vm.frame().exit_early() {
1018 return ControlFlow::Break(CompletionRecord::Normal(result));
1019 }
1020
1021 self.vm.stack.push(result);
1022 self.vm.pop_frame().expect("frame must exist");
1023 ControlFlow::Continue(())
1024 }
1025
1026 fn handle_throw(&mut self) -> ControlFlow<CompletionRecord> {
1027 if self
1028 .vm
1029 .pending_exception
1030 .as_ref()
1031 .is_some_and(|err| err.backtrace.is_none())
1032 {
1033 let pc = self.vm.frames.last().expect("frame must exist").pc;
1034 let limit = self.vm.runtime_limits.backtrace_limit();
1035 let backtrace = self.vm.shadow_stack.take(limit, pc);
1036 self.vm
1037 .pending_exception
1038 .as_mut()
1039 .expect("pending exception must exist")
1040 .backtrace = Some(backtrace);
1041 }
1042
1043 let mut env_fp = self.vm.frame().env_fp;
1044 if self.vm.frame().exit_early() {
1045 self.vm.frame_mut().environments.truncate(env_fp as usize);
1046 let frame = self.vm.frames.last().expect("frame must exist");
1047 self.vm.stack.truncate_to_frame(frame);
1048 return ControlFlow::Break(CompletionRecord::Throw(
1049 self.vm
1050 .pending_exception
1051 .take()
1052 .expect("Err must exist for a CompletionType::Throw"),
1053 ));
1054 }
1055
1056 let mut frame = self.vm.pop_frame().expect("frame must exist");
1057
1058 loop {
1059 env_fp = self.vm.frame().env_fp;
1060 let pc = self.vm.frame().pc;
1061 let exit_early = self.vm.frame().exit_early();
1062
1063 if self.vm.handle_exception_at(pc) {
1064 return ControlFlow::Continue(());
1065 }
1066
1067 if exit_early {
1068 return ControlFlow::Break(CompletionRecord::Throw(
1069 self.vm
1070 .pending_exception
1071 .take()
1072 .expect("Err must exist for a CompletionType::Throw"),
1073 ));
1074 }
1075
1076 let Some(f) = self.vm.pop_frame() else {
1077 break;
1078 };
1079 frame = f;
1080 }
1081 self.vm.frame_mut().environments.truncate(env_fp as usize);
1082 self.vm.stack.truncate_to_frame(&frame);
1083 ControlFlow::Continue(())
1084 }
1085
1086 #[allow(clippy::future_not_send)]
1089 pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord {
1090 let _execution = self.enter_execution_time_limit();
1091 let mut runtime_budget: u32 = budget;
1092
1093 while let Some(byte) = self
1094 .vm
1095 .frame()
1096 .code_block
1097 .bytecode
1098 .bytes
1099 .get(self.vm.frame().pc as usize)
1100 {
1101 let opcode = Opcode::decode(*byte);
1102
1103 match self.execute_one(
1104 |context, opcode| {
1105 let frame = context.vm.frame();
1106 let pc = frame.pc as usize;
1107
1108 OPCODE_HANDLERS_BUDGET[opcode as usize](context, pc, &mut runtime_budget)
1109 },
1110 opcode,
1111 ) {
1112 ControlFlow::Continue(()) => {}
1113 ControlFlow::Break(value) => return value,
1114 }
1115
1116 if runtime_budget == 0 {
1117 runtime_budget = budget;
1118 yield_now().await;
1119 }
1120 }
1121
1122 CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
1123 }
1124
1125 pub(crate) fn run(&mut self) -> CompletionRecord {
1126 let _execution = self.enter_execution_time_limit();
1127 while let Some(byte) = self
1128 .vm
1129 .frame()
1130 .code_block
1131 .bytecode
1132 .bytes
1133 .get(self.vm.frame().pc as usize)
1134 {
1135 let opcode = Opcode::decode(*byte);
1136
1137 match self.execute_one(
1138 |context, opcode| {
1139 let frame = context.vm.frame();
1140 let pc = frame.pc as usize;
1141
1142 OPCODE_HANDLERS[opcode as usize](context, pc)
1143 },
1144 opcode,
1145 ) {
1146 ControlFlow::Continue(()) => {}
1147 ControlFlow::Break(value) => return value,
1148 }
1149 }
1150
1151 CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
1152 }
1153
1154 pub(crate) fn check_runtime_limits(&self) -> JsResult<()> {
1156 let js_depth = self.vm.frames.len().saturating_sub(1);
1162 if self.vm.runtime_limits.recursion_limit() <= js_depth
1163 || self.vm.runtime_limits.host_recursion_limit() <= self.vm.host_call_depth
1164 {
1165 return Err(JsNativeError::range()
1166 .with_message("Maximum call stack size exceeded")
1167 .into());
1168 }
1169 if self.vm.runtime_limits.stack_size_limit() <= self.vm.stack.stack.len() {
1173 return Err(JsNativeError::range()
1174 .with_message("Maximum call stack size exceeded")
1175 .into());
1176 }
1177
1178 Ok(())
1179 }
1180}
1181
1182fn yield_now() -> impl Future<Output = ()> {
1184 struct YieldNow(bool);
1185
1186 impl Future for YieldNow {
1187 type Output = ();
1188
1189 fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
1190 if self.0 {
1191 task::Poll::Ready(())
1192 } else {
1193 self.0 = true;
1194 cx.waker().wake_by_ref();
1195 task::Poll::Pending
1196 }
1197 }
1198 }
1199
1200 YieldNow(false)
1201}