#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::error::{VmError, VmResult};
use crate::opcodes::flags;
const ALLOC_HEADER_SIZE: usize = 8;
const ALLOCATED_FLAG: u64 = 0x8000_0000_0000_0000;
const SIZE_MASK: u64 = !ALLOCATED_FLAG;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FreeBlock {
pub addr: usize,
pub size: usize,
}
pub const MAX_STACK_SIZE: usize = 1024;
pub const MAX_INSTRUCTIONS: u64 = 1_000_000;
pub const MAX_REGISTERS: usize = 256;
pub const DEFAULT_REGISTER_CAPACITY: usize = 32;
pub const DEFAULT_HEAP_SIZE: usize = 1024 * 1024;
pub const MAX_HEAP_SIZE: usize = 10 * 1024 * 1024;
pub const DEFAULT_HEAP_CAPACITY: usize = 4 * 1024;
pub const INPUT_BASE_ADDR: u64 = 0x8000_0000;
pub const OUTPUT_BASE_ADDR: u64 = 0xC000_0000;
#[derive(Debug, Clone)]
pub struct VmState<'a> {
pub regs: Vec<u64>,
pub heap: Vec<u8>,
pub heap_ptr: usize,
pub heap_limit: usize,
pub free_list: Vec<FreeBlock>,
pub stack: Vec<u64>,
pub call_stack: Vec<usize>,
pub ip: usize,
pub flags: u8,
pub instruction_count: u64,
pub halted: bool,
pub result: u64,
pub last_error: VmError,
pub code: &'a [u8],
pub input: &'a [u8],
pub output: Vec<u8>,
pub last_timing_ns: u64,
pub start_time_ns: u64,
#[allow(clippy::type_complexity)]
pub native_table: Option<&'a [fn(&[u64]) -> u64]>,
#[cfg(feature = "async_vm")]
pub yield_mask: u64,
}
impl<'a> VmState<'a> {
pub fn new(code: &'a [u8], input: &'a [u8]) -> Self {
Self {
regs: vec![0u64; DEFAULT_REGISTER_CAPACITY],
heap: Vec::with_capacity(DEFAULT_HEAP_CAPACITY),
heap_ptr: 0,
heap_limit: DEFAULT_HEAP_SIZE,
free_list: Vec::with_capacity(16), stack: Vec::with_capacity(64),
call_stack: Vec::with_capacity(16),
ip: 0,
flags: 0,
instruction_count: 0,
halted: false,
result: 0,
last_error: VmError::Ok,
code,
input,
output: Vec::new(),
last_timing_ns: 0,
start_time_ns: 0,
native_table: None,
#[cfg(feature = "async_vm")]
yield_mask: crate::build_config::YIELD_MASK,
}
}
pub fn with_heap_limit(code: &'a [u8], input: &'a [u8], heap_limit: usize) -> Self {
let mut state = Self::new(code, input);
state.heap_limit = heap_limit.min(MAX_HEAP_SIZE);
state
}
pub fn with_code_and_state(code: &'a [u8], input: &'a [u8], old: &VmState<'a>) -> Self {
Self {
regs: old.regs.clone(),
heap: old.heap.clone(),
heap_ptr: old.heap_ptr,
heap_limit: old.heap_limit,
free_list: old.free_list.clone(),
stack: old.stack.clone(),
call_stack: old.call_stack.clone(),
ip: old.ip,
flags: old.flags,
instruction_count: old.instruction_count,
halted: old.halted,
result: old.result,
last_error: old.last_error,
code,
input,
output: old.output.clone(),
last_timing_ns: old.last_timing_ns,
start_time_ns: old.start_time_ns,
native_table: old.native_table,
#[cfg(feature = "async_vm")]
yield_mask: old.yield_mask,
}
}
#[inline]
pub fn init_timing(&mut self) {
#[cfg(all(feature = "std", not(feature = "vm_debug")))]
{
use std::time::{SystemTime, UNIX_EPOCH};
self.start_time_ns = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
self.last_timing_ns = self.start_time_ns;
}
#[cfg(any(not(feature = "std"), feature = "vm_debug"))]
{
self.start_time_ns = 0;
self.last_timing_ns = 0;
}
}
#[inline]
pub fn current_time_ns(&self) -> u64 {
#[cfg(all(feature = "std", not(feature = "vm_debug")))]
{
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0)
}
#[cfg(any(not(feature = "std"), feature = "vm_debug"))]
{
0
}
}
pub fn reset(&mut self) {
self.regs.clear();
self.regs.resize(DEFAULT_REGISTER_CAPACITY, 0);
self.heap.clear();
self.heap_ptr = 0;
self.free_list.clear();
self.stack.clear();
self.call_stack.clear();
self.ip = 0;
self.flags = 0;
self.instruction_count = 0;
self.halted = false;
self.result = 0;
self.last_error = VmError::Ok;
self.output.clear();
self.last_timing_ns = 0;
self.start_time_ns = 0;
self.native_table = None;
#[cfg(feature = "async_vm")]
{
self.yield_mask = crate::build_config::YIELD_MASK;
}
}
#[cfg(feature = "async_vm")]
#[inline]
pub fn get_yield_mask(&self) -> u64 {
self.yield_mask
}
#[cfg(not(feature = "async_vm"))]
#[inline]
pub fn get_yield_mask(&self) -> u64 {
0xFF }
#[cfg(feature = "async_vm")]
#[inline]
pub fn set_yield_mask(&mut self, mask: u64) {
self.yield_mask = mask;
}
#[inline]
pub fn set_native_table(&mut self, table: &'a [fn(&[u64]) -> u64]) {
self.native_table = Some(table);
}
#[inline]
pub fn get_native_fn(&self, index: usize) -> Option<fn(&[u64]) -> u64> {
self.native_table.and_then(|t| t.get(index).copied())
}
#[inline]
pub fn push(&mut self, value: u64) -> VmResult<()> {
if self.stack.len() >= MAX_STACK_SIZE {
return Err(VmError::StackOverflow);
}
self.stack.push(value);
Ok(())
}
#[inline]
pub fn pop(&mut self) -> VmResult<u64> {
self.stack.pop().ok_or(VmError::StackUnderflow)
}
#[inline]
pub fn peek(&self) -> VmResult<u64> {
self.stack.last().copied().ok_or(VmError::StackUnderflow)
}
#[inline]
pub fn stack_len(&self) -> usize {
self.stack.len()
}
#[inline]
pub fn get_reg(&self, idx: u8) -> VmResult<u64> {
let index = idx as usize;
if index < self.regs.len() {
Ok(self.regs[index])
} else {
Ok(0)
}
}
#[inline]
pub fn set_reg(&mut self, idx: u8, value: u64) -> VmResult<()> {
let index = idx as usize;
if index >= self.regs.len() {
if index >= MAX_REGISTERS {
return Err(VmError::InvalidRegister);
}
self.regs.resize(index + 1, 0);
}
self.regs[index] = value;
Ok(())
}
#[inline]
pub fn reg_count(&self) -> usize {
self.regs.len()
}
#[inline]
pub fn heap_alloc(&mut self, size: usize) -> VmResult<u64> {
let aligned_user_size = (size + 7) & !7;
let total_size = ALLOC_HEADER_SIZE + aligned_user_size;
if let Some(idx) = self.find_free_block(total_size) {
let block = self.free_list.remove(idx);
let user_addr = block.addr + ALLOC_HEADER_SIZE;
let header = (total_size as u64) | ALLOCATED_FLAG;
self.heap_write_u64_internal(block.addr, header);
let remaining = block.size - total_size;
if remaining >= ALLOC_HEADER_SIZE + 8 {
self.insert_free_block_sorted(FreeBlock {
addr: block.addr + total_size,
size: remaining,
});
}
return Ok(user_addr as u64);
}
let new_ptr = self.heap_ptr + total_size;
if new_ptr > self.heap_limit {
return Err(VmError::HeapOutOfMemory);
}
if new_ptr > self.heap.len() {
self.heap.resize(new_ptr, 0);
}
let block_addr = self.heap_ptr;
let header = (total_size as u64) | ALLOCATED_FLAG;
self.heap_write_u64_internal(block_addr, header);
let user_addr = block_addr + ALLOC_HEADER_SIZE;
self.heap_ptr = new_ptr;
Ok(user_addr as u64)
}
#[inline]
fn find_free_block(&self, total_size: usize) -> Option<usize> {
self.free_list
.iter()
.position(|block| block.size >= total_size)
}
#[inline]
fn heap_write_u64_internal(&mut self, addr: usize, value: u64) {
let bytes = value.to_le_bytes();
self.heap[addr..addr + 8].copy_from_slice(&bytes);
}
pub fn heap_free(&mut self, user_addr: usize) -> VmResult<()> {
if user_addr < ALLOC_HEADER_SIZE {
return Err(VmError::HeapOutOfBounds);
}
let header_addr = user_addr - ALLOC_HEADER_SIZE;
let header = self.heap_read_u64(header_addr)?;
if header & ALLOCATED_FLAG == 0 {
return Err(VmError::DoubleFree);
}
let total_size = (header & SIZE_MASK) as usize;
if total_size == 0 || total_size > self.heap_ptr {
return Err(VmError::HeapOutOfBounds);
}
self.heap_write_u64_internal(header_addr, total_size as u64);
let new_block = FreeBlock {
addr: header_addr,
size: total_size,
};
self.add_free_block_with_merge(new_block);
Ok(())
}
fn insert_free_block_sorted(&mut self, block: FreeBlock) {
let pos = self.free_list
.binary_search_by_key(&block.addr, |b| b.addr)
.unwrap_or_else(|i| i);
self.free_list.insert(pos, block);
}
fn add_free_block_with_merge(&mut self, mut block: FreeBlock) {
let pos = self.free_list
.binary_search_by_key(&block.addr, |b| b.addr)
.unwrap_or_else(|i| i);
if pos > 0 {
let prev = &self.free_list[pos - 1];
if prev.addr + prev.size == block.addr {
block.addr = prev.addr;
block.size += prev.size;
self.free_list.remove(pos - 1);
return self.add_free_block_with_merge(block);
}
}
if pos < self.free_list.len() {
let next = &self.free_list[pos];
if block.addr + block.size == next.addr {
block.size += next.size;
self.free_list.remove(pos);
return self.add_free_block_with_merge(block);
}
}
self.free_list.insert(pos, block);
}
#[inline]
pub fn heap_free_space(&self) -> usize {
let free_list_space: usize = self.free_list.iter().map(|b| b.size).sum();
let bump_space = self.heap_limit.saturating_sub(self.heap_ptr);
free_list_space + bump_space
}
#[inline]
pub fn free_block_count(&self) -> usize {
self.free_list.len()
}
#[inline]
pub fn heap_read_u8(&self, addr: usize) -> VmResult<u8> {
if addr >= self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
Ok(self.heap[addr])
}
#[inline]
pub fn heap_read_u16(&self, addr: usize) -> VmResult<u16> {
if addr + 2 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
Ok(u16::from_le_bytes([self.heap[addr], self.heap[addr + 1]]))
}
#[inline]
pub fn heap_read_u32(&self, addr: usize) -> VmResult<u32> {
if addr + 4 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
Ok(u32::from_le_bytes([
self.heap[addr],
self.heap[addr + 1],
self.heap[addr + 2],
self.heap[addr + 3],
]))
}
#[inline]
pub fn heap_read_u64(&self, addr: usize) -> VmResult<u64> {
if addr + 8 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
Ok(u64::from_le_bytes([
self.heap[addr],
self.heap[addr + 1],
self.heap[addr + 2],
self.heap[addr + 3],
self.heap[addr + 4],
self.heap[addr + 5],
self.heap[addr + 6],
self.heap[addr + 7],
]))
}
#[inline]
pub fn heap_write_u8(&mut self, addr: usize, value: u8) -> VmResult<()> {
if addr >= self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
self.heap[addr] = value;
Ok(())
}
#[inline]
pub fn heap_write_u16(&mut self, addr: usize, value: u16) -> VmResult<()> {
if addr + 2 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
let bytes = value.to_le_bytes();
self.heap[addr..addr + 2].copy_from_slice(&bytes);
Ok(())
}
#[inline]
pub fn heap_write_u32(&mut self, addr: usize, value: u32) -> VmResult<()> {
if addr + 4 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
let bytes = value.to_le_bytes();
self.heap[addr..addr + 4].copy_from_slice(&bytes);
Ok(())
}
#[inline]
pub fn heap_write_u64(&mut self, addr: usize, value: u64) -> VmResult<()> {
if addr + 8 > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
let bytes = value.to_le_bytes();
self.heap[addr..addr + 8].copy_from_slice(&bytes);
Ok(())
}
#[inline]
pub fn heap_write_bytes(&mut self, addr: usize, data: &[u8]) -> VmResult<()> {
if addr + data.len() > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
self.heap[addr..addr + data.len()].copy_from_slice(data);
Ok(())
}
#[inline]
pub fn heap_read_bytes(&self, addr: usize, len: usize) -> VmResult<&[u8]> {
if addr + len > self.heap.len() {
return Err(VmError::HeapOutOfBounds);
}
Ok(&self.heap[addr..addr + len])
}
#[inline]
pub fn heap_size(&self) -> usize {
self.heap_ptr
}
#[inline]
pub fn heap_used(&self) -> usize {
self.heap_ptr
}
#[inline]
pub fn heap_remaining(&self) -> usize {
self.heap_limit.saturating_sub(self.heap_ptr)
}
#[inline]
pub fn set_zero_flag(&mut self, value: u64) {
if value == 0 {
self.flags |= flags::ZERO;
} else {
self.flags &= !flags::ZERO;
}
}
#[inline]
pub fn set_sign_flag(&mut self, value: u64) {
if (value as i64) < 0 {
self.flags |= flags::SIGN;
} else {
self.flags &= !flags::SIGN;
}
}
#[inline]
pub fn is_zero(&self) -> bool {
self.flags & flags::ZERO != 0
}
#[inline]
pub fn is_negative(&self) -> bool {
self.flags & flags::SIGN != 0
}
#[inline]
pub fn is_carry(&self) -> bool {
self.flags & flags::CARRY != 0
}
#[inline]
pub fn is_overflow(&self) -> bool {
self.flags & flags::OVERFLOW != 0
}
pub fn update_cmp_flags(&mut self, a: u64, b: u64) {
let result = a.wrapping_sub(b);
self.set_zero_flag(result);
self.set_sign_flag(result);
if a < b {
self.flags |= flags::CARRY;
} else {
self.flags &= !flags::CARRY;
}
let sa = (a as i64) < 0;
let sb = (b as i64) < 0;
let sr = (result as i64) < 0;
if (sa != sb) && (sr != sa) {
self.flags |= flags::OVERFLOW;
} else {
self.flags &= !flags::OVERFLOW;
}
}
#[inline]
pub fn read_u8(&mut self) -> VmResult<u8> {
if self.ip >= self.code.len() {
return Err(VmError::InvalidBytecode);
}
let val = self.code[self.ip];
self.ip += 1;
Ok(val)
}
#[inline]
pub fn read_i16(&mut self) -> VmResult<i16> {
if self.ip + 2 > self.code.len() {
return Err(VmError::InvalidBytecode);
}
let val = i16::from_le_bytes([self.code[self.ip], self.code[self.ip + 1]]);
self.ip += 2;
Ok(val)
}
#[inline]
pub fn read_u16(&mut self) -> VmResult<u16> {
if self.ip + 2 > self.code.len() {
return Err(VmError::InvalidBytecode);
}
let val = u16::from_le_bytes([self.code[self.ip], self.code[self.ip + 1]]);
self.ip += 2;
Ok(val)
}
#[inline]
pub fn read_u32(&mut self) -> VmResult<u32> {
if self.ip + 4 > self.code.len() {
return Err(VmError::InvalidBytecode);
}
let val = u32::from_le_bytes([
self.code[self.ip],
self.code[self.ip + 1],
self.code[self.ip + 2],
self.code[self.ip + 3],
]);
self.ip += 4;
Ok(val)
}
#[inline]
pub fn read_u64(&mut self) -> VmResult<u64> {
if self.ip + 8 > self.code.len() {
return Err(VmError::InvalidBytecode);
}
let val = u64::from_le_bytes([
self.code[self.ip],
self.code[self.ip + 1],
self.code[self.ip + 2],
self.code[self.ip + 3],
self.code[self.ip + 4],
self.code[self.ip + 5],
self.code[self.ip + 6],
self.code[self.ip + 7],
]);
self.ip += 8;
Ok(val)
}
#[inline]
pub fn read_input(&self, offset: usize) -> VmResult<u8> {
if offset >= self.input.len() {
return Err(VmError::MemoryOutOfBounds);
}
Ok(self.input[offset])
}
#[inline]
pub fn read_input_u8(&self, offset: usize) -> VmResult<u8> {
self.read_input(offset)
}
#[inline]
pub fn read_input_u16(&self, offset: usize) -> VmResult<u16> {
if offset + 2 > self.input.len() {
return Err(VmError::MemoryOutOfBounds);
}
Ok(u16::from_le_bytes([
self.input[offset],
self.input[offset + 1],
]))
}
#[inline]
pub fn read_input_u32(&self, offset: usize) -> VmResult<u32> {
if offset + 4 > self.input.len() {
return Err(VmError::MemoryOutOfBounds);
}
Ok(u32::from_le_bytes([
self.input[offset],
self.input[offset + 1],
self.input[offset + 2],
self.input[offset + 3],
]))
}
#[inline]
pub fn read_input_u64(&self, offset: usize) -> VmResult<u64> {
if offset + 8 > self.input.len() {
return Err(VmError::MemoryOutOfBounds);
}
Ok(u64::from_le_bytes([
self.input[offset],
self.input[offset + 1],
self.input[offset + 2],
self.input[offset + 3],
self.input[offset + 4],
self.input[offset + 5],
self.input[offset + 6],
self.input[offset + 7],
]))
}
#[inline]
pub fn write_output_u8(&mut self, offset: usize, value: u8) -> VmResult<()> {
if offset >= self.output.len() {
self.output.resize(offset + 1, 0);
}
self.output[offset] = value;
Ok(())
}
#[inline]
pub fn write_output_u16(&mut self, offset: usize, value: u16) -> VmResult<()> {
if offset + 2 > self.output.len() {
self.output.resize(offset + 2, 0);
}
let bytes = value.to_le_bytes();
self.output[offset] = bytes[0];
self.output[offset + 1] = bytes[1];
Ok(())
}
#[inline]
pub fn write_output_u32(&mut self, offset: usize, value: u32) -> VmResult<()> {
if offset + 4 > self.output.len() {
self.output.resize(offset + 4, 0);
}
let bytes = value.to_le_bytes();
self.output[offset] = bytes[0];
self.output[offset + 1] = bytes[1];
self.output[offset + 2] = bytes[2];
self.output[offset + 3] = bytes[3];
Ok(())
}
#[inline]
pub fn write_output_u64(&mut self, offset: usize, value: u64) -> VmResult<()> {
if offset + 8 > self.output.len() {
self.output.resize(offset + 8, 0);
}
let bytes = value.to_le_bytes();
self.output[offset] = bytes[0];
self.output[offset + 1] = bytes[1];
self.output[offset + 2] = bytes[2];
self.output[offset + 3] = bytes[3];
self.output[offset + 4] = bytes[4];
self.output[offset + 5] = bytes[5];
self.output[offset + 6] = bytes[6];
self.output[offset + 7] = bytes[7];
Ok(())
}
#[inline]
pub fn input_len(&self) -> usize {
self.input.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamic_registers() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
assert_eq!(state.reg_count(), DEFAULT_REGISTER_CAPACITY);
assert_eq!(state.get_reg(100).unwrap(), 0);
state.set_reg(100, 42).unwrap();
assert!(state.reg_count() > 100);
assert_eq!(state.get_reg(100).unwrap(), 42);
state.set_reg(255, 999).unwrap();
assert_eq!(state.get_reg(255).unwrap(), 999);
}
#[test]
fn test_heap_allocation() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let addr1 = state.heap_alloc(100).unwrap();
assert_eq!(addr1, 8); assert_eq!(state.heap_used(), 112);
let addr2 = state.heap_alloc(50).unwrap();
assert_eq!(addr2, 120); assert_eq!(state.heap_used(), 176); }
#[test]
fn test_heap_read_write() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let addr = state.heap_alloc(16).unwrap() as usize;
state.heap_write_u64(addr, 0xDEADBEEF_CAFEBABE).unwrap();
state.heap_write_u8(addr + 8, 42).unwrap();
assert_eq!(state.heap_read_u64(addr).unwrap(), 0xDEADBEEF_CAFEBABE);
assert_eq!(state.heap_read_u8(addr + 8).unwrap(), 42);
}
#[test]
fn test_heap_limit() {
let code = &[];
let input = &[];
let mut state = VmState::with_heap_limit(code, input, 200);
state.heap_alloc(50).unwrap();
state.heap_alloc(100).unwrap();
let result = state.heap_alloc(50);
assert_eq!(result, Err(VmError::HeapOutOfMemory));
}
#[test]
fn test_reset() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
state.set_reg(100, 42).unwrap();
state.heap_alloc(1000).unwrap();
state.push(123).unwrap();
state.reset();
assert_eq!(state.reg_count(), DEFAULT_REGISTER_CAPACITY);
assert_eq!(state.heap_used(), 0);
assert_eq!(state.stack_len(), 0);
assert_eq!(state.free_block_count(), 0);
}
#[test]
fn test_heap_free_basic() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let addr = state.heap_alloc(100).unwrap() as usize;
let heap_after_alloc = state.heap_used();
assert_eq!(state.free_block_count(), 0);
state.heap_free(addr).unwrap();
assert_eq!(state.free_block_count(), 1);
assert_eq!(state.heap_used(), heap_after_alloc);
}
#[test]
fn test_heap_free_reuse() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let addr1 = state.heap_alloc(100).unwrap() as usize;
let addr2 = state.heap_alloc(100).unwrap() as usize;
let _addr3 = state.heap_alloc(100).unwrap();
let heap_after_allocs = state.heap_used();
state.heap_free(addr2).unwrap();
assert_eq!(state.free_block_count(), 1);
let addr4 = state.heap_alloc(100).unwrap() as usize;
assert_eq!(addr4, addr2); assert_eq!(state.heap_used(), heap_after_allocs);
state.heap_free(addr1).unwrap();
let addr5 = state.heap_alloc(50).unwrap() as usize;
assert_eq!(addr5, addr1); }
#[test]
fn test_heap_free_merge_adjacent() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let addr1 = state.heap_alloc(64).unwrap() as usize;
let addr2 = state.heap_alloc(64).unwrap() as usize;
let addr3 = state.heap_alloc(64).unwrap() as usize;
state.heap_free(addr1).unwrap();
assert_eq!(state.free_block_count(), 1);
state.heap_free(addr3).unwrap();
assert_eq!(state.free_block_count(), 2);
state.heap_free(addr2).unwrap();
assert_eq!(state.free_block_count(), 1);
let big_addr = state.heap_alloc(200).unwrap() as usize;
assert_eq!(big_addr, addr1); }
#[test]
fn test_heap_free_invalid() {
let code = &[];
let input = &[];
let mut state = VmState::new(code, input);
let result = state.heap_free(4);
assert_eq!(result, Err(VmError::HeapOutOfBounds));
let _addr = state.heap_alloc(100).unwrap();
let result = state.heap_free(0);
assert_eq!(result, Err(VmError::HeapOutOfBounds));
}
#[test]
fn test_heap_free_space() {
let code = &[];
let input = &[];
let mut state = VmState::with_heap_limit(code, input, 1000);
let initial_free = state.heap_free_space();
assert_eq!(initial_free, 1000);
let addr = state.heap_alloc(100).unwrap() as usize;
assert_eq!(state.heap_free_space(), 1000 - 112);
state.heap_free(addr).unwrap();
assert_eq!(state.heap_free_space(), 1000 - 112 + 112); }
}