use crate::{
Context, JsError, JsExpect, JsNativeError, JsObject, JsResult, JsString, JsValue, Module,
builtins::promise::{PromiseCapability, ResolvingFunctions},
environments::EnvironmentStack,
error::RuntimeLimitError,
object::JsFunction,
realm::Realm,
script::Script,
vm::opcode::{OPCODE_HANDLERS, OPCODE_HANDLERS_BUDGET},
vm::shadow_stack::ErrorStack,
};
use boa_gc::{Finalize, Gc, Trace, custom_trace};
use shadow_stack::ShadowStack;
use std::{future::Future, ops::ControlFlow, path::Path, pin::Pin, task};
#[cfg(feature = "trace")]
use crate::sys::time::Instant;
#[allow(unused_imports)]
pub(crate) use opcode::{Instruction, InstructionIterator, Opcode};
pub(crate) use {
call_frame::CallFrameFlags,
code_block::{
CodeBlockFlags, Constant, Handler, create_function_object, create_function_object_fast,
},
completion_record::CompletionRecord,
inline_cache::InlineCache,
};
pub use runtime_limits::RuntimeLimits;
pub use {
call_frame::{CallFrame, GeneratorResumeKind},
code_block::CodeBlock,
source_info::{NativeSourceInfo, SourcePath},
};
pub(crate) use code_block::GlobalFunctionBinding;
mod call_frame;
mod code_block;
mod completion_record;
mod inline_cache;
mod runtime_limits;
pub(crate) mod opcode;
pub(crate) mod shadow_stack;
pub(crate) mod source_info;
#[cfg(feature = "flowgraph")]
pub mod flowgraph;
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub struct Vm {
pub(crate) frames: Vec<CallFrame>,
pub(crate) stack: Stack,
pub(crate) return_value: JsValue,
pub(crate) pending_exception: Option<JsError>,
pub(crate) runtime_limits: RuntimeLimits,
pub(crate) native_active_function: Option<JsObject>,
pub(crate) host_call_depth: usize,
pub(crate) shadow_stack: ShadowStack,
#[cfg(feature = "trace")]
pub(crate) trace: bool,
#[cfg(feature = "trace")]
pub(crate) current_frame: Option<*const CallFrame>,
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct Stack {
stack: Vec<JsValue>,
}
impl Stack {
fn new(capacity: usize) -> Self {
Self {
stack: Vec::with_capacity(capacity),
}
}
pub(crate) fn get_register(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
self.stack.get(frame.rp as usize + index)
}
pub(crate) fn set_register(&mut self, frame: &CallFrame, index: usize, value: JsValue) {
self.stack[frame.rp as usize + index] = value;
}
pub(crate) fn truncate_to_frame(&mut self, frame: &CallFrame) {
self.stack.truncate(frame.frame_pointer());
}
pub(crate) fn split_off_frame(&mut self, frame: &CallFrame) -> Self {
let frame_pointer = frame.frame_pointer();
Self {
stack: self.stack.split_off(frame_pointer),
}
}
pub(crate) fn get_this(&self, frame: &CallFrame) -> JsValue {
self.stack[frame.this_index()].clone()
}
pub(crate) fn set_this(&mut self, frame: &CallFrame, this: JsValue) {
self.stack[frame.this_index()] = this;
}
pub(crate) fn get_function(&self, frame: &CallFrame) -> Option<JsObject> {
if let Some(object) = self.stack[frame.function_index()].as_object() {
return Some(object.clone());
}
None
}
pub(crate) fn get_arguments(&self, frame: &CallFrame) -> &[JsValue] {
&self.stack[frame.arguments_range()]
}
pub(crate) fn get_argument(&self, frame: &CallFrame, index: usize) -> Option<&JsValue> {
self.get_arguments(frame).get(index)
}
pub(crate) fn pop_rest_arguments(&mut self, frame: &CallFrame) -> Option<Vec<JsValue>> {
let argument_count = frame.argument_count as usize;
let param_count = frame.code_block().parameter_length as usize;
if argument_count < param_count {
return None;
}
let args_start = frame.fp as usize + CallFrame::FUNCTION_PROLOGUE as usize;
let args_end = args_start + argument_count;
let rest_count = argument_count - param_count + 1;
Some(
self.stack
.drain((args_end - rest_count)..args_end)
.collect(),
)
}
pub(crate) fn push<T>(&mut self, value: T)
where
T: Into<JsValue>,
{
self.stack.push(value.into());
}
#[track_caller]
pub(crate) fn pop(&mut self) -> JsValue {
self.stack.pop().expect("stack was empty")
}
pub(crate) fn calling_convention_pop_arguments(
&mut self,
argument_count: usize,
) -> Vec<JsValue> {
let index = self.stack.len() - argument_count;
self.stack.split_off(index)
}
pub(crate) fn calling_convention_push_arguments(&mut self, values: &[JsValue]) {
self.stack.extend_from_slice(values);
}
#[track_caller]
pub(crate) fn calling_convention_get_function(&self, argument_count: usize) -> &JsValue {
let index = self.stack.len() - 1 - argument_count;
self.stack
.get(index)
.expect("invalid calling convention function index")
}
#[track_caller]
pub(crate) fn calling_convention_set_function(
&mut self,
argument_count: usize,
function: JsValue,
) {
let index = self.stack.len() - 1 - argument_count;
self.stack[index] = function;
}
#[track_caller]
pub(crate) fn calling_convention_set_this(&mut self, argument_count: usize, function: JsValue) {
let index = self.stack.len() - 2 - argument_count;
self.stack[index] = function;
}
pub(crate) fn calling_convention_insert_arguments(
&mut self,
existing_argument_count: usize,
arguments: &[JsValue],
) {
let index = self.stack.len() - existing_argument_count;
self.stack.splice(index..index, arguments.iter().cloned());
}
#[cfg(feature = "trace")]
const MAX_VALUE_LEN: usize = 18;
#[cfg(feature = "trace")]
const MAX_STACK_WIDTH: usize = 68;
#[cfg(feature = "trace")]
fn raw_value(value: &JsValue) -> String {
match value {
v if v.is_callable() => "func".to_string(),
v if v.is_object() => "obj".to_string(),
v if v.is_undefined() => "und".to_string(),
v if v.is_null() => "null".to_string(),
v => v.display().to_string(),
}
}
#[cfg(feature = "trace")]
fn truncate_display(val: &str) -> String {
if val.len() <= Self::MAX_VALUE_LEN {
return val.to_string();
}
let mut end = Self::MAX_VALUE_LEN - 2;
while !val.is_char_boundary(end) && end > 0 {
end -= 1;
}
format!("{}..", &val[..end])
}
#[cfg(feature = "trace")]
fn display_trace(&self, frame: &CallFrame, frame_count: usize) -> String {
let total = self.stack.len();
if total == 0 {
return "[ <empty> ]".to_string();
}
let mut groups: Vec<(String, usize, Option<usize>)> = Vec::new();
let mut force_truncate = false;
for (idx, v) in self.stack.iter().enumerate().rev() {
let is_frame = frame.frame_pointer() == idx;
let raw = Self::raw_value(v);
if !is_frame
&& let Some(last) = groups.last_mut()
&& last.0 == raw
&& last.2.is_none()
{
last.1 += 1;
} else {
let marker = if is_frame { Some(frame_count) } else { None };
groups.push((raw, 1, marker));
if groups.len() > Self::MAX_STACK_WIDTH / 2 {
force_truncate = true;
break;
}
}
}
let mut string = String::from("[ ");
let mut truncated = force_truncate;
let suffix = format!(".. ({total} total) ]");
for (i, (val, count, marker)) in groups.iter().enumerate() {
let display_val = Self::truncate_display(val);
let part = if *count > 1 {
format!("{display_val} (x{count})")
} else {
display_val
};
let separator = if let Some(fc) = marker {
format!(" |{fc}|")
} else if i + 1 < groups.len() {
",".to_string()
} else {
String::new()
};
let addition = format!("{part}{separator} ");
if string.len() + addition.len() + suffix.len() > Self::MAX_STACK_WIDTH {
truncated = true;
break;
}
string.push_str(&addition);
}
if truncated {
string.push_str(&suffix);
} else {
string.push(']');
}
string
}
}
#[derive(Debug, Clone, Finalize)]
pub enum ActiveRunnable {
Script(Script),
Module(Module),
}
unsafe impl Trace for ActiveRunnable {
custom_trace!(this, mark, {
match this {
Self::Script(script) => mark(script),
Self::Module(module) => mark(module),
}
});
}
impl ActiveRunnable {
#[must_use]
pub fn path(&self) -> Option<&Path> {
match self {
Self::Script(script) => script.path(),
Self::Module(module) => module.path(),
}
}
}
impl Vm {
pub(crate) fn new(realm: Realm) -> Self {
let mut frames = Vec::with_capacity(16);
frames.push(CallFrame::new(
Gc::new(CodeBlock::new(JsString::default(), 0, true)),
None,
EnvironmentStack::new(),
realm,
));
Self {
frames,
stack: Stack::new(1024),
return_value: JsValue::undefined(),
pending_exception: None,
runtime_limits: RuntimeLimits::default(),
native_active_function: None,
host_call_depth: 0,
shadow_stack: ShadowStack::default(),
#[cfg(feature = "trace")]
trace: false,
#[cfg(feature = "trace")]
current_frame: None,
}
}
#[track_caller]
#[inline]
pub(crate) fn set_register(&mut self, index: usize, value: JsValue) {
let rp = self.frame().rp as usize;
debug_assert!(
rp + index < self.stack.stack.len(),
"register index out of bounds: rp {rp}, index {index}, stack len {}",
self.stack.stack.len()
);
unsafe {
*self.stack.stack.get_unchecked_mut(rp + index) = value;
}
}
#[track_caller]
#[inline]
pub(crate) fn get_register(&self, index: usize) -> &JsValue {
let rp = self.frame().rp as usize;
debug_assert!(
rp + index < self.stack.stack.len(),
"register index out of bounds: rp {rp}, index {index}, stack len {}",
self.stack.stack.len()
);
unsafe { self.stack.stack.get_unchecked(rp + index) }
}
#[track_caller]
#[inline]
pub(crate) fn take_register(&mut self, index: usize) -> JsValue {
let rp = self.frame().rp as usize;
debug_assert!(
rp + index < self.stack.stack.len(),
"register index out of bounds: rp {rp}, index {index}, stack len {}",
self.stack.stack.len()
);
unsafe { std::mem::take(self.stack.stack.get_unchecked_mut(rp + index)) }
}
#[track_caller]
pub(crate) fn set_promise_capability(
&mut self,
promise_capability: PromiseCapability,
) -> JsResult<()> {
#[cfg(debug_assertions)]
{
if !self.frame().code_block().is_async() {
return Err(crate::error::PanicError::new(
"only async functions and modules with a top-level-await \
can have a promise capability",
)
.into());
}
}
let rp = self.frame().rp as usize;
self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX] =
promise_capability.promise.into();
self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX] =
promise_capability.functions.resolve.into();
self.stack.stack[rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX] =
promise_capability.functions.reject.into();
Ok(())
}
#[track_caller]
pub(crate) fn get_promise_capability(&self) -> JsResult<PromiseCapability> {
#[cfg(debug_assertions)]
if !self.frame().code_block().is_async() {
return Err(crate::error::PanicError::new(
"cannot get promise capability from non-async code",
)
.into());
}
let rp = self.frame().rp as usize;
let promise = self
.stack
.stack
.get(rp + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX)
.and_then(JsValue::as_object)
.js_expect("registers must have a promise capability")?;
let resolve = self
.stack
.stack
.get(rp + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX)
.and_then(JsValue::as_object)
.and_then(JsFunction::from_object)
.js_expect("registers must have a resolve function")?;
let reject = self
.stack
.stack
.get(rp + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX)
.and_then(JsValue::as_object)
.and_then(JsFunction::from_object)
.js_expect("registers must have a reject function")?;
Ok(PromiseCapability {
promise,
functions: ResolvingFunctions { resolve, reject },
})
}
#[track_caller]
pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
if !self.frame().code_block().is_async_generator() {
return None;
}
let rp = self.frame().rp as usize;
self.stack
.stack
.get(rp + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX)
.expect("registers must have an async generator object")
.as_object()
}
#[track_caller]
#[inline]
pub(crate) fn frame(&self) -> &CallFrame {
unsafe { self.frames.last().unwrap_unchecked() }
}
#[track_caller]
#[inline]
pub(crate) fn frame_mut(&mut self) -> &mut CallFrame {
unsafe { self.frames.last_mut().unwrap_unchecked() }
}
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
self.return_value = JsValue::undefined();
if !frame.registers_already_pushed() {
let current_stack_length = self.stack.stack.len() as u32;
frame.fp = current_stack_length - frame.argument_count - CallFrame::FUNCTION_PROLOGUE;
let register_count = frame.code_block.register_count as usize;
frame.rp = self.stack.stack.len() as u32;
self.stack.stack.resize(
self.stack.stack.len() + register_count,
JsValue::undefined(),
);
}
if frame.active_runnable.is_none() {
let current = self.frame();
frame.active_runnable.clone_from(¤t.active_runnable);
}
let current_pc = self.frame().pc;
self.shadow_stack
.push_bytecode(current_pc, frame.code_block().source_info.clone());
self.frames.push(frame);
}
pub(crate) fn push_frame_with_stack(
&mut self,
frame: CallFrame,
this: JsValue,
function: JsValue,
) {
self.stack.push(this);
self.stack.push(function);
self.push_frame(frame);
}
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
if self.frames.len() <= 1 {
return None;
}
self.shadow_stack.pop();
self.frames.pop()
}
#[inline]
pub(crate) fn handle_exception_at(&mut self, pc: u32) -> bool {
let frame = self.frame_mut();
let Some((_, handler)) = frame.code_block().find_handler(pc) else {
return false;
};
let catch_address = handler.handler();
let environment_sp = frame.env_fp + handler.environment_count;
frame.pc = u32::from(catch_address);
self.frame_mut()
.environments
.truncate(environment_sp as usize);
true
}
pub(crate) fn get_return_value(&self) -> JsValue {
self.return_value.clone()
}
pub(crate) fn set_return_value(&mut self, value: JsValue) {
self.return_value = value;
}
pub(crate) fn take_return_value(&mut self) -> JsValue {
std::mem::take(&mut self.return_value)
}
}
#[allow(clippy::print_stdout)]
#[cfg(feature = "trace")]
impl Context {
const COLUMN_WIDTH: usize = 26;
const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
pub(crate) fn trace_call_frame(&self) {
let frame = self.vm.frame();
let msg = if self.vm.frames.is_empty() {
" VM Start ".to_string()
} else {
format!(
" Call Frame '{}'{} ",
frame.code_block().name().to_std_string_escaped(),
if frame.code_block().name().is_empty() {
format!(" [anon#{}]", frame.code_block().debug_id)
} else {
String::new()
}
)
};
if !frame.code_block.traced.get() {
println!("{}", frame.code_block);
frame.code_block.traced.set(true);
}
println!(
"{msg:-^width$}",
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
);
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
"Time",
"Opcode",
"Operands",
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
);
}
fn trace_execute_instruction<F>(
&mut self,
f: F,
opcode: Opcode,
) -> ControlFlow<CompletionRecord>
where
F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
{
if self.vm.current_frame != Some(self.vm.frame()) {
println!();
self.trace_call_frame();
self.vm.current_frame = Some(self.vm.frame());
}
let frame = self.vm.frame();
let (instruction, _) = frame
.code_block
.bytecode
.next_instruction(frame.pc as usize);
let operands = self
.vm
.frame()
.code_block()
.instruction_operands(&instruction);
let instant = Instant::now();
let result = self.execute_instruction(f, opcode);
let duration = instant.elapsed();
let stack = self
.vm
.stack
.display_trace(self.vm.frame(), self.vm.frames.len() - 1);
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
format!("{}μs", duration.as_micros()),
opcode.as_str().to_string(),
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
);
result
}
}
impl Context {
fn execute_instruction<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
where
F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
{
f(self, opcode)
}
fn execute_one<F>(&mut self, f: F, opcode: Opcode) -> ControlFlow<CompletionRecord>
where
F: FnOnce(&mut Context, Opcode) -> ControlFlow<CompletionRecord>,
{
#[cfg(feature = "fuzz")]
{
use crate::error::EngineError;
if self.instructions_remaining == 0 {
return ControlFlow::Break(CompletionRecord::Throw(
EngineError::NoInstructionsRemain.into(),
));
}
self.instructions_remaining -= 1;
}
#[cfg(feature = "trace")]
if self.vm.trace || self.vm.frame().code_block.traceable() {
self.trace_execute_instruction(f, opcode)
} else {
self.execute_instruction(f, opcode)
}
#[cfg(not(feature = "trace"))]
self.execute_instruction(f, opcode)
}
fn handle_error(&mut self, mut err: JsError) -> ControlFlow<CompletionRecord> {
if err.backtrace.is_none() {
err.backtrace = Some(
self.vm
.shadow_stack
.take(self.vm.runtime_limits.backtrace_limit(), self.vm.frame().pc),
);
}
if !err.is_catchable() {
let mut frame = None;
let mut env_fp = self.vm.frame().environments.len();
loop {
if self.vm.frame().exit_early() {
break;
}
env_fp = self.vm.frame().env_fp as usize;
let Some(f) = self.vm.pop_frame() else {
break;
};
frame = Some(f);
}
self.vm.frame_mut().environments.truncate(env_fp);
if let Some(frame) = frame {
self.vm.stack.truncate_to_frame(&frame);
}
return ControlFlow::Break(CompletionRecord::Throw(err));
}
if let Some(native) = err.as_native_mut()
&& let ErrorStack::Position(position) = &mut native.stack.0
{
let backtrace = self.vm.shadow_stack.take_and_push(
self.vm.runtime_limits.backtrace_limit(),
self.vm.frame().pc,
position.clone(),
);
native.stack.0 = ErrorStack::Backtrace(backtrace);
}
let pc = self.vm.frame().pc.saturating_sub(1);
if self.vm.handle_exception_at(pc) {
self.vm.pending_exception = Some(err);
return ControlFlow::Continue(());
}
let err = err.inject_realm(self.realm().clone());
self.vm.pending_exception = Some(err);
self.handle_throw()
}
fn handle_return(&mut self) -> ControlFlow<CompletionRecord> {
let exit_early = self.vm.frame().exit_early();
let frame = self.vm.frames.last().expect("frame must exist");
self.vm.stack.truncate_to_frame(frame);
let result = self.vm.take_return_value();
if exit_early {
return ControlFlow::Break(CompletionRecord::Return(result));
}
self.vm.stack.push(result);
self.vm.pop_frame().expect("frame must exist");
ControlFlow::Continue(())
}
fn handle_yield(&mut self) -> ControlFlow<CompletionRecord> {
let result = self.vm.take_return_value();
if self.vm.frame().exit_early() {
return ControlFlow::Break(CompletionRecord::Normal(result));
}
self.vm.stack.push(result);
self.vm.pop_frame().expect("frame must exist");
ControlFlow::Continue(())
}
fn handle_throw(&mut self) -> ControlFlow<CompletionRecord> {
if self
.vm
.pending_exception
.as_ref()
.is_some_and(|err| err.backtrace.is_none())
{
let pc = self.vm.frames.last().expect("frame must exist").pc;
let limit = self.vm.runtime_limits.backtrace_limit();
let backtrace = self.vm.shadow_stack.take(limit, pc);
self.vm
.pending_exception
.as_mut()
.expect("pending exception must exist")
.backtrace = Some(backtrace);
}
let mut env_fp = self.vm.frame().env_fp;
if self.vm.frame().exit_early() {
self.vm.frame_mut().environments.truncate(env_fp as usize);
let frame = self.vm.frames.last().expect("frame must exist");
self.vm.stack.truncate_to_frame(frame);
return ControlFlow::Break(CompletionRecord::Throw(
self.vm
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
));
}
let mut frame = self.vm.pop_frame().expect("frame must exist");
loop {
env_fp = self.vm.frame().env_fp;
let pc = self.vm.frame().pc;
let exit_early = self.vm.frame().exit_early();
if self.vm.handle_exception_at(pc) {
return ControlFlow::Continue(());
}
if exit_early {
return ControlFlow::Break(CompletionRecord::Throw(
self.vm
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
));
}
let Some(f) = self.vm.pop_frame() else {
break;
};
frame = f;
}
self.vm.frame_mut().environments.truncate(env_fp as usize);
self.vm.stack.truncate_to_frame(&frame);
ControlFlow::Continue(())
}
#[allow(clippy::future_not_send)]
pub(crate) async fn run_async_with_budget(&mut self, budget: u32) -> CompletionRecord {
let mut runtime_budget: u32 = budget;
while let Some(byte) = self
.vm
.frame()
.code_block
.bytecode
.bytes
.get(self.vm.frame().pc as usize)
{
let opcode = Opcode::decode(*byte);
match self.execute_one(
|context, opcode| {
let frame = context.vm.frame();
let pc = frame.pc as usize;
OPCODE_HANDLERS_BUDGET[opcode as usize](context, pc, &mut runtime_budget)
},
opcode,
) {
ControlFlow::Continue(()) => {}
ControlFlow::Break(value) => return value,
}
if runtime_budget == 0 {
runtime_budget = budget;
yield_now().await;
}
}
CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
}
pub(crate) fn run(&mut self) -> CompletionRecord {
while let Some(byte) = self
.vm
.frame()
.code_block
.bytecode
.bytes
.get(self.vm.frame().pc as usize)
{
let opcode = Opcode::decode(*byte);
match self.execute_one(
|context, opcode| {
let frame = context.vm.frame();
let pc = frame.pc as usize;
OPCODE_HANDLERS[opcode as usize](context, pc)
},
opcode,
) {
ControlFlow::Continue(()) => {}
ControlFlow::Break(value) => return value,
}
}
CompletionRecord::Throw(JsError::from_native(JsNativeError::error()))
}
pub(crate) fn check_runtime_limits(&self) -> JsResult<()> {
let recursion_depth = (self.vm.frames.len() - 1).saturating_add(self.vm.host_call_depth);
if self.vm.runtime_limits.recursion_limit() <= recursion_depth {
return Err(RuntimeLimitError::Recursion.into());
}
if self.vm.runtime_limits.stack_size_limit() <= self.vm.stack.stack.len() {
return Err(RuntimeLimitError::StackSize.into());
}
Ok(())
}
}
fn yield_now() -> impl Future<Output = ()> {
struct YieldNow(bool);
impl Future for YieldNow {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
if self.0 {
task::Poll::Ready(())
} else {
self.0 = true;
cx.waker().wake_by_ref();
task::Poll::Pending
}
}
}
YieldNow(false)
}