#![allow(dead_code)]
use crate::builtins::BuiltinFunction;
use crate::bytecode::{Value, ValueType, VmMap};
use crate::vm::{HostCallExecOutcome, NumericValue, Vm, VmError, VmResult, logical_shr_i64};
use std::sync::Arc;
use std::sync::{Mutex, OnceLock};
pub(crate) const STATUS_CONTINUE: i32 = 0;
pub(crate) const STATUS_HALTED: i32 = 1;
pub(crate) const STATUS_TRACE_EXIT: i32 = 2;
pub(crate) const STATUS_YIELDED: i32 = 3;
pub(crate) const STATUS_WAITING: i32 = 4;
pub(crate) const STATUS_OUT_OF_FUEL: i32 = 5;
pub(crate) const STATUS_LINKED_CONTINUE: i32 = 6;
pub(crate) const STATUS_ERROR: i32 = -1;
pub(crate) const OP_LDC: i64 = 1;
pub(crate) const OP_ADD: i64 = 2;
pub(crate) const OP_SUB: i64 = 3;
pub(crate) const OP_MUL: i64 = 4;
pub(crate) const OP_DIV: i64 = 5;
pub(crate) const OP_MOD: i64 = 6;
pub(crate) const OP_SHL: i64 = 7;
pub(crate) const OP_SHR: i64 = 8;
pub(crate) const OP_LSHR: i64 = 9;
pub(crate) const OP_AND: i64 = 10;
pub(crate) const OP_OR: i64 = 11;
pub(crate) const OP_NOT: i64 = 12;
pub(crate) const OP_NEG: i64 = 13;
pub(crate) const OP_CEQ: i64 = 14;
pub(crate) const OP_CLT: i64 = 15;
pub(crate) const OP_CGT: i64 = 16;
pub(crate) const OP_POP: i64 = 17;
pub(crate) const OP_DUP: i64 = 18;
pub(crate) const OP_LDLOC: i64 = 19;
pub(crate) const OP_STLOC: i64 = 20;
pub(crate) const OP_CALL: i64 = 21;
pub(crate) const OP_GUARD_FALSE: i64 = 22;
pub(crate) const OP_JUMP: i64 = 23;
pub(crate) const OP_BUILTIN_CALL: i64 = 24;
pub(crate) const OP_GUARD_TRUE: i64 = 25;
pub(crate) const OP_LOOP_IF_FALSE: i64 = 26;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) enum NativeInterruptMode {
Fuel,
Epoch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(crate) struct NativeInterruptSettings {
pub(crate) mode: NativeInterruptMode,
pub(crate) check_interval: u32,
}
impl NativeInterruptSettings {
pub(crate) const fn fuel(check_interval: u32) -> Self {
Self {
mode: NativeInterruptMode::Fuel,
check_interval,
}
}
pub(crate) const fn epoch(check_interval: u32) -> Self {
Self {
mode: NativeInterruptMode::Epoch,
check_interval,
}
}
}
static GENERIC_BRIDGE_ERROR: OnceLock<Mutex<Option<VmError>>> = OnceLock::new();
fn generic_bridge_error_cell() -> &'static Mutex<Option<VmError>> {
GENERIC_BRIDGE_ERROR.get_or_init(|| Mutex::new(None))
}
pub(crate) fn store_bridge_error(error: VmError) {
if let Ok(mut guard) = generic_bridge_error_cell().lock() {
*guard = Some(error);
}
}
pub(crate) fn clear_bridge_error() {
if let Ok(mut guard) = generic_bridge_error_cell().lock() {
*guard = None;
}
}
pub(crate) fn take_bridge_error() -> Option<VmError> {
if let Ok(mut guard) = generic_bridge_error_cell().lock() {
return guard.take();
}
None
}
fn arc_repr_word<T>(value: &Arc<T>) -> usize {
debug_assert_eq!(std::mem::size_of::<Arc<T>>(), std::mem::size_of::<usize>());
unsafe { *(value as *const Arc<T> as *const usize) }
}
fn arc_into_repr_ptr<T>(value: Arc<T>) -> *mut u8 {
let ptr = arc_repr_word(&value) as *mut u8;
std::mem::forget(value);
ptr
}
unsafe fn arc_from_repr_ptr<T>(ptr: *mut u8) -> Arc<T> {
debug_assert_eq!(
std::mem::size_of::<Arc<T>>(),
std::mem::size_of::<*mut u8>()
);
unsafe { std::mem::transmute_copy(&ptr) }
}
fn run_step<F>(vm: *mut Vm, helper_name: &str, f: F) -> i32
where
F: FnOnce(&mut Vm) -> VmResult<i32>,
{
let Some(vm_ref) = (unsafe { vm.as_mut() }) else {
store_bridge_error(VmError::JitNative(format!(
"native {helper_name} helper received null vm pointer"
)));
return STATUS_ERROR;
};
match f(vm_ref) {
Ok(status) => status,
Err(err) => {
store_bridge_error(err);
STATUS_ERROR
}
}
}
fn bridge_name_for_op(op: i64) -> Option<&'static str> {
match op {
OP_LDC => Some("ldc"),
OP_ADD => Some("add"),
OP_SUB => Some("sub"),
OP_MUL => Some("mul"),
OP_DIV => Some("div"),
OP_MOD => Some("mod"),
OP_SHL => Some("shl"),
OP_SHR => Some("shr"),
OP_LSHR => Some("lshr"),
OP_AND => Some("and"),
OP_OR => Some("or"),
OP_NOT => Some("not"),
OP_NEG => Some("neg"),
OP_CEQ => Some("ceq"),
OP_CLT => Some("clt"),
OP_CGT => Some("cgt"),
OP_POP => Some("pop"),
OP_DUP => Some("dup"),
OP_LDLOC => Some("ldloc"),
OP_STLOC => Some("stloc"),
OP_CALL => Some("call"),
OP_BUILTIN_CALL => Some("builtin_call"),
OP_GUARD_FALSE => Some("guard_false"),
OP_GUARD_TRUE => Some("guard_true"),
OP_LOOP_IF_FALSE => Some("loop_if_false"),
OP_JUMP => Some("jump_ip"),
_ => None,
}
}
pub(crate) fn helper_entry_address() -> usize {
pd_vm_native_step as *const () as usize
}
pub(crate) fn interrupt_helper_entry_address() -> usize {
pd_vm_native_interrupt_tick as *const () as usize
}
pub(crate) fn aot_call_boundary_interrupt_entry_address() -> usize {
pd_vm_native_aot_call_boundary_interrupt as *const () as usize
}
pub(crate) fn alloc_byte_buffer_entry_address() -> usize {
pd_vm_native_alloc_byte_buffer as *const () as usize
}
pub(crate) fn alloc_value_buffer_entry_address() -> usize {
pd_vm_native_alloc_value_buffer as *const () as usize
}
pub(crate) fn shared_string_from_buffer_entry_address() -> usize {
pd_vm_native_shared_string_from_buffer as *const () as usize
}
pub(crate) fn shared_bytes_from_buffer_entry_address() -> usize {
pd_vm_native_shared_bytes_from_buffer as *const () as usize
}
pub(crate) fn shared_array_from_buffer_entry_address() -> usize {
pd_vm_native_shared_array_from_buffer as *const () as usize
}
pub(crate) fn copy_bytes_entry_address() -> usize {
pd_vm_native_copy_bytes as *const () as usize
}
pub(crate) fn zero_bytes_entry_address() -> usize {
pd_vm_native_zero_bytes as *const () as usize
}
pub(crate) fn clone_value_to_slot_entry_address() -> usize {
pd_vm_native_clone_value_to_slot as *const () as usize
}
pub(crate) fn init_null_value_slot_entry_address() -> usize {
pd_vm_native_init_null_value_slot as *const () as usize
}
pub(crate) fn clear_value_slot_entry_address() -> usize {
pd_vm_native_clear_value_slot as *const () as usize
}
pub(crate) fn value_eq_entry_address() -> usize {
pd_vm_native_value_eq as *const () as usize
}
pub(crate) fn write_heap_value_to_slot_entry_address() -> usize {
pd_vm_native_write_heap_value_to_slot as *const () as usize
}
pub(crate) fn restore_exit_state_entry_address() -> usize {
pd_vm_native_restore_exit_state as *const () as usize
}
pub(crate) fn map_has_entry_address() -> usize {
pd_vm_native_map_has as *const () as usize
}
pub(crate) fn map_get_entry_address() -> usize {
pd_vm_native_map_get as *const () as usize
}
pub(crate) fn helper_entry_offset() -> i32 {
i32::try_from(std::mem::offset_of!(Vm, native_helper_fn))
.expect("Vm::native_helper_fn offset must fit i32")
}
pub(crate) fn interrupt_helper_entry_offset() -> i32 {
i32::try_from(std::mem::offset_of!(Vm, native_interrupt_helper_fn))
.expect("Vm::native_interrupt_helper_fn offset must fit i32")
}
pub(crate) extern "C" fn pd_vm_native_interrupt_tick(vm: *mut Vm) -> i32 {
let Some(vm_ref) = (unsafe { vm.as_mut() }) else {
store_bridge_error(VmError::JitNative(
"native interrupt helper received null vm pointer".to_string(),
));
return STATUS_ERROR;
};
match vm_ref.charge_interrupt_tick() {
Ok(()) => STATUS_CONTINUE,
Err(VmError::OutOfFuel { .. } | VmError::EpochDeadlineReached { .. }) => STATUS_OUT_OF_FUEL,
Err(err) => {
store_bridge_error(err);
STATUS_ERROR
}
}
}
pub(crate) extern "C" fn pd_vm_native_aot_call_boundary_interrupt(vm: *mut Vm) -> i32 {
let Some(vm_ref) = (unsafe { vm.as_mut() }) else {
store_bridge_error(VmError::JitNative(
"native aot call-boundary interrupt helper received null vm pointer".to_string(),
));
return STATUS_ERROR;
};
match vm_ref.charge_aot_call_boundary_interrupt() {
Ok(()) => STATUS_CONTINUE,
Err(VmError::OutOfFuel { .. } | VmError::EpochDeadlineReached { .. }) => STATUS_OUT_OF_FUEL,
Err(err) => {
store_bridge_error(err);
STATUS_ERROR
}
}
}
pub(crate) extern "C" fn pd_vm_native_alloc_byte_buffer(cap: usize) -> *mut u8 {
let mut buffer = Vec::<u8>::with_capacity(cap);
let ptr = buffer.as_mut_ptr();
std::mem::forget(buffer);
ptr
}
pub(crate) extern "C" fn pd_vm_native_alloc_value_buffer(cap: usize) -> *mut Value {
let mut buffer = Vec::<Value>::with_capacity(cap);
let ptr = buffer.as_mut_ptr();
std::mem::forget(buffer);
ptr
}
pub(crate) extern "C" fn pd_vm_native_shared_string_from_buffer(
ptr: *mut u8,
len: usize,
cap: usize,
) -> *mut u8 {
let bytes = unsafe { Vec::<u8>::from_raw_parts(ptr, len, cap) };
let text = unsafe { String::from_utf8_unchecked(bytes) };
arc_into_repr_ptr(Arc::new(text))
}
pub(crate) extern "C" fn pd_vm_native_shared_bytes_from_buffer(
ptr: *mut u8,
len: usize,
cap: usize,
) -> *mut u8 {
let bytes = unsafe { Vec::<u8>::from_raw_parts(ptr, len, cap) };
arc_into_repr_ptr(Arc::new(bytes))
}
pub(crate) extern "C" fn pd_vm_native_shared_array_from_buffer(
ptr: *mut Value,
len: usize,
cap: usize,
) -> *mut u8 {
let values = unsafe { Vec::<Value>::from_raw_parts(ptr, len, cap) };
arc_into_repr_ptr(Arc::new(values))
}
pub(crate) extern "C" fn pd_vm_native_copy_bytes(dst: *mut u8, src: *const u8, len: usize) {
unsafe {
std::ptr::copy_nonoverlapping(src, dst, len);
}
}
pub(crate) extern "C" fn pd_vm_native_zero_bytes(dst: *mut u8, len: usize) {
unsafe {
std::ptr::write_bytes(dst, 0, len);
}
}
unsafe fn clone_arc_from_repr_ptr<T>(ptr: *mut u8) -> Arc<T> {
let arc = unsafe { arc_from_repr_ptr::<T>(ptr) };
let cloned = arc.clone();
std::mem::forget(arc);
cloned
}
pub(crate) extern "C" fn pd_vm_native_clone_value_to_slot(
dst: *mut Value,
src: *const Value,
) -> i32 {
if dst.is_null() || src.is_null() {
store_bridge_error(VmError::JitNative(
"native clone-value helper received null slot pointer".to_string(),
));
return STATUS_ERROR;
}
unsafe {
std::ptr::write(dst, (*src).clone());
}
STATUS_CONTINUE
}
pub(crate) extern "C" fn pd_vm_native_init_null_value_slot(dst: *mut Value) -> i32 {
if dst.is_null() {
store_bridge_error(VmError::JitNative(
"native init-null-slot helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
unsafe {
std::ptr::write(dst, Value::Null);
}
STATUS_CONTINUE
}
pub(crate) extern "C" fn pd_vm_native_clear_value_slot(dst: *mut Value) -> i32 {
if dst.is_null() {
store_bridge_error(VmError::JitNative(
"native clear-slot helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
unsafe {
let old = std::mem::replace(&mut *dst, Value::Null);
drop(old);
}
STATUS_CONTINUE
}
pub(crate) extern "C" fn pd_vm_native_value_eq(lhs: *const Value, rhs: *const Value) -> i32 {
if lhs.is_null() || rhs.is_null() {
store_bridge_error(VmError::JitNative(
"native value-eq helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
i32::from(unsafe { *lhs == *rhs })
}
pub(crate) extern "C" fn pd_vm_native_write_heap_value_to_slot(
dst: *mut Value,
repr_ptr: *mut u8,
tag: i64,
) -> i32 {
if dst.is_null() || repr_ptr.is_null() {
store_bridge_error(VmError::JitNative(
"native box-heap helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
let value = match tag {
x if x == ValueType::String as i64 => {
Value::String(unsafe { clone_arc_from_repr_ptr::<String>(repr_ptr) })
}
x if x == ValueType::Bytes as i64 => {
Value::Bytes(unsafe { clone_arc_from_repr_ptr::<Vec<u8>>(repr_ptr) })
}
x if x == ValueType::Array as i64 => {
Value::Array(unsafe { clone_arc_from_repr_ptr::<Vec<Value>>(repr_ptr) })
}
x if x == ValueType::Map as i64 => {
Value::Map(unsafe { clone_arc_from_repr_ptr::<VmMap>(repr_ptr) })
}
_ => {
store_bridge_error(VmError::JitNative(format!(
"native box-heap helper received unsupported ValueType tag {tag}"
)));
return STATUS_ERROR;
}
};
unsafe {
std::ptr::write(dst, value);
}
STATUS_CONTINUE
}
pub(crate) extern "C" fn pd_vm_native_restore_exit_state(
vm: *mut Vm,
stack_src: *const Value,
stack_len: usize,
locals_src: *const Value,
locals_len: usize,
ip: usize,
) -> i32 {
run_step(vm, "restore_exit_state", |vm| {
if locals_len != vm.locals.len() {
return Err(VmError::JitNative(format!(
"native exit restore locals length mismatch: expected {}, got {}",
vm.locals.len(),
locals_len
)));
}
if stack_len != 0 && stack_src.is_null() {
return Err(VmError::JitNative(
"native exit restore received null stack buffer".to_string(),
));
}
if locals_len != 0 && locals_src.is_null() {
return Err(VmError::JitNative(
"native exit restore received null locals buffer".to_string(),
));
}
vm.clear_stack_with_drop_contract();
vm.stack.reserve(stack_len);
for index in 0..stack_len {
let value = unsafe { std::ptr::read(stack_src.add(index)) };
vm.stack.push(value);
}
for index in 0..locals_len {
let local_index = u8::try_from(index).map_err(|_| {
VmError::JitNative("native exit restore local index out of range".to_string())
})?;
let value = unsafe { std::ptr::read(locals_src.add(index)) };
vm.store_local_with_drop_contract(local_index, value)?;
}
vm.jump_to(ip)?;
Ok(STATUS_CONTINUE)
})
}
pub(crate) extern "C" fn pd_vm_native_map_has(repr_ptr: *mut u8, key: *const Value) -> i32 {
if repr_ptr.is_null() || key.is_null() {
store_bridge_error(VmError::JitNative(
"native map-has helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
let entries = unsafe { arc_from_repr_ptr::<VmMap>(repr_ptr) };
let present = entries.get(unsafe { &*key }).is_some();
std::mem::forget(entries);
i32::from(present)
}
pub(crate) extern "C" fn pd_vm_native_map_get(
dst: *mut Value,
repr_ptr: *mut u8,
key: *const Value,
) -> i32 {
if dst.is_null() || repr_ptr.is_null() || key.is_null() {
store_bridge_error(VmError::JitNative(
"native map-get helper received null pointer".to_string(),
));
return STATUS_ERROR;
}
let entries = unsafe { arc_from_repr_ptr::<VmMap>(repr_ptr) };
let Some(value) = entries.get(unsafe { &*key }) else {
std::mem::forget(entries);
return 0;
};
unsafe {
std::ptr::write(dst, value.clone());
}
std::mem::forget(entries);
1
}
pub(crate) extern "C" fn pd_vm_native_step(vm: *mut Vm, op: i64, a: i64, b: i64, c: i64) -> i32 {
run_step(vm, "step", |vm| {
if op == OP_BUILTIN_CALL {
let bridge_name = u16::try_from(a)
.ok()
.and_then(BuiltinFunction::from_call_index)
.map(BuiltinFunction::name)
.unwrap_or("builtin_call");
vm.record_native_bridge_hit(bridge_name);
} else if let Some(name) = bridge_name_for_op(op) {
vm.record_native_bridge_hit(name);
}
match op {
OP_LDC => {
let index = u32::try_from(a)
.map_err(|_| VmError::JitNative("ldc index out of range".to_string()))?;
let value = vm
.program
.constants
.get(index as usize)
.cloned()
.ok_or(VmError::InvalidConstant(index))?;
vm.stack.push(value);
Ok(STATUS_CONTINUE)
}
OP_ADD => {
vm.binary_add_op()?;
Ok(STATUS_CONTINUE)
}
OP_SUB => {
vm.binary_numeric_op(
|lhs, rhs| Ok(lhs.wrapping_sub(rhs)),
|lhs, rhs| Ok(lhs - rhs),
)?;
Ok(STATUS_CONTINUE)
}
OP_MUL => {
vm.binary_numeric_op(
|lhs, rhs| Ok(lhs.wrapping_mul(rhs)),
|lhs, rhs| Ok(lhs * rhs),
)?;
Ok(STATUS_CONTINUE)
}
OP_DIV => {
vm.binary_numeric_op(crate::vm::checked_int_div, |lhs, rhs| Ok(lhs / rhs))?;
Ok(STATUS_CONTINUE)
}
OP_MOD => {
vm.binary_numeric_op(crate::vm::checked_int_rem, |lhs, rhs| Ok(lhs % rhs))?;
Ok(STATUS_CONTINUE)
}
OP_SHL => {
let rhs = vm.pop_shift_amount()?;
let lhs = vm.pop_int()?;
vm.stack
.push(crate::bytecode::Value::Int(lhs.wrapping_shl(rhs)));
Ok(STATUS_CONTINUE)
}
OP_SHR => {
let rhs = vm.pop_shift_amount()?;
let lhs = vm.pop_int()?;
vm.stack
.push(crate::bytecode::Value::Int(lhs.wrapping_shr(rhs)));
Ok(STATUS_CONTINUE)
}
OP_LSHR => {
let rhs = vm.pop_shift_amount()?;
let lhs = vm.pop_int()?;
vm.stack
.push(crate::bytecode::Value::Int(logical_shr_i64(lhs, rhs)));
Ok(STATUS_CONTINUE)
}
OP_AND => {
let rhs = vm.pop_bool()?;
let lhs = vm.pop_bool()?;
vm.stack.push(crate::bytecode::Value::Bool(lhs && rhs));
Ok(STATUS_CONTINUE)
}
OP_OR => {
let rhs = vm.pop_bool()?;
let lhs = vm.pop_bool()?;
vm.stack.push(crate::bytecode::Value::Bool(lhs || rhs));
Ok(STATUS_CONTINUE)
}
OP_NOT => {
vm.unary_not_op()?;
Ok(STATUS_CONTINUE)
}
OP_NEG => {
let value = vm.pop_numeric()?;
match value {
NumericValue::Int(value) => vm
.stack
.push(crate::bytecode::Value::Int(value.wrapping_neg())),
NumericValue::Float(value) => {
vm.stack.push(crate::bytecode::Value::Float(-value))
}
}
Ok(STATUS_CONTINUE)
}
OP_CEQ => {
let rhs = vm.pop_value()?;
let lhs = vm.pop_value()?;
vm.stack.push(crate::bytecode::Value::Bool(lhs == rhs));
Ok(STATUS_CONTINUE)
}
OP_CLT => {
vm.compare_numeric_op(|lhs, rhs| lhs < rhs, |lhs, rhs| lhs < rhs)?;
Ok(STATUS_CONTINUE)
}
OP_CGT => {
vm.compare_numeric_op(|lhs, rhs| lhs > rhs, |lhs, rhs| lhs > rhs)?;
Ok(STATUS_CONTINUE)
}
OP_POP => {
vm.pop_value()?;
Ok(STATUS_CONTINUE)
}
OP_DUP => {
let value = vm.peek_value()?.clone();
vm.stack.push(value);
Ok(STATUS_CONTINUE)
}
OP_LDLOC => {
let index = u8::try_from(a)
.map_err(|_| VmError::JitNative("ldloc index out of range".to_string()))?;
let value = vm
.locals
.get(index as usize)
.cloned()
.ok_or(VmError::InvalidLocal(index))?;
vm.stack.push(value);
Ok(STATUS_CONTINUE)
}
OP_STLOC => {
let index = u8::try_from(a)
.map_err(|_| VmError::JitNative("stloc index out of range".to_string()))?;
let value = vm.pop_value()?;
vm.store_local_with_drop_contract(index, value)?;
Ok(STATUS_CONTINUE)
}
OP_CALL => {
let index = u16::try_from(a)
.map_err(|_| VmError::JitNative("call index out of range".to_string()))?;
let argc = u8::try_from(b)
.map_err(|_| VmError::JitNative("call argc out of range".to_string()))?;
let call_ip = usize::try_from(c)
.map_err(|_| VmError::JitNative("call ip out of range".to_string()))?;
match vm.execute_host_call(index, argc, call_ip)? {
HostCallExecOutcome::Returned => Ok(STATUS_CONTINUE),
HostCallExecOutcome::Halted => Ok(STATUS_HALTED),
HostCallExecOutcome::Yielded => Ok(STATUS_YIELDED),
HostCallExecOutcome::Pending(_) => Ok(STATUS_WAITING),
}
}
OP_BUILTIN_CALL => {
let index = u16::try_from(a).map_err(|_| {
VmError::JitNative("builtin call index out of range".to_string())
})?;
let argc = u8::try_from(b).map_err(|_| {
VmError::JitNative("builtin call argc out of range".to_string())
})?;
let call_ip = usize::try_from(c)
.map_err(|_| VmError::JitNative("builtin call ip out of range".to_string()))?;
match vm.execute_host_call(index, argc, call_ip)? {
HostCallExecOutcome::Returned => Ok(STATUS_CONTINUE),
HostCallExecOutcome::Halted => Ok(STATUS_HALTED),
HostCallExecOutcome::Yielded => Ok(STATUS_YIELDED),
HostCallExecOutcome::Pending(_) => Ok(STATUS_WAITING),
}
}
OP_GUARD_FALSE => {
let exit_ip = usize::try_from(a)
.map_err(|_| VmError::JitNative("guard exit ip out of range".to_string()))?;
let condition = vm.pop_bool()?;
if !condition {
vm.jump_to(exit_ip)?;
return Ok(STATUS_TRACE_EXIT);
}
Ok(STATUS_CONTINUE)
}
OP_GUARD_TRUE => {
let exit_ip = usize::try_from(a)
.map_err(|_| VmError::JitNative("guard exit ip out of range".to_string()))?;
let condition = vm.pop_bool()?;
if condition {
vm.jump_to(exit_ip)?;
return Ok(STATUS_TRACE_EXIT);
}
Ok(STATUS_CONTINUE)
}
OP_LOOP_IF_FALSE => {
let exit_ip = usize::try_from(a)
.map_err(|_| VmError::JitNative("guard exit ip out of range".to_string()))?;
let condition = vm.pop_bool()?;
if condition {
vm.jump_to(exit_ip)?;
return Ok(STATUS_TRACE_EXIT);
}
Ok(STATUS_CONTINUE)
}
OP_JUMP => {
let target_ip = usize::try_from(a)
.map_err(|_| VmError::JitNative("jump target out of range".to_string()))?;
vm.jump_to(target_ip)?;
Ok(STATUS_TRACE_EXIT)
}
_ => Err(VmError::JitNative(format!(
"native step helper received unsupported op id {op}"
))),
}
})
}