use super::super::target::{Native, native};
use super::{Registers, CELL, Beetle};
pub const MEMORY_CELLS: u32 = 1 << 20;
pub const DATA_CELLS: u32 = 1 << 18;
pub const RETURN_CELLS: u32 = 1 << 18;
pub struct VM {
beetle: Option<super::Beetle<Native>>,
state: Registers,
memory: Vec<u32>,
free_cells: u32,
halt_addr: u32,
}
impl VM {
pub fn new(
memory_cells: u32,
data_cells: u32,
return_cells: u32,
) -> Self {
let mut vm = VM {
beetle: Some(Beetle::new(native())),
state: Registers::default(),
memory: vec![0; memory_cells as usize],
free_cells: memory_cells,
halt_addr: 0,
};
let rp = vm.allocate(return_cells).1;
vm.registers_mut().rp = rp;
let sp = vm.allocate(data_cells).1;
vm.registers_mut().sp = sp;
vm.halt_addr = vm.allocate(1).0;
vm.store(vm.halt_addr, 0x5519);
vm
}
pub fn registers(&self) -> &Registers { &self.state }
pub fn registers_mut(&mut self) -> &mut Registers { &mut self.state }
pub fn memory(&self) -> &[u32] { &self.memory }
pub fn allocate(&mut self, cells: u32) -> (u32, u32) {
assert!(cells <= self.free_cells);
let end = self.free_cells.checked_mul(CELL)
.expect("Address out of range");
self.free_cells = self.free_cells.checked_sub(cells)
.expect("Out of memory");
let start = self.free_cells.checked_mul(CELL)
.expect("Address out of range");
(start, end)
}
pub fn load_object(&mut self, object: &[u32]) {
assert!(object.len() <= self.free_cells as usize);
for (i, &cell) in object.iter().enumerate() {
self.memory[i] = cell;
}
}
pub fn load(&self, addr: u32) -> u32 {
assert_eq!(addr & 0x3, 0);
self.memory[(addr >> 2) as usize]
}
pub fn store(&mut self, addr: u32, value: u32) {
assert_eq!(addr & 0x3, 0);
self.memory[(addr >> 2) as usize] = value;
}
pub fn push(&mut self, item: u32) {
self.registers_mut().sp -= CELL;
self.store(self.registers().sp, item);
}
pub fn pop(&mut self) -> u32 {
let item = self.load(self.registers().sp);
self.registers_mut().sp += CELL;
item
}
pub fn rpush(&mut self, item: u32) {
self.registers_mut().rp -= CELL;
self.store(self.registers().rp, item);
}
pub unsafe fn run(&mut self, ep: u32) -> Option<u32> {
assert!(Self::is_aligned(ep));
self.registers_mut().ep = ep;
let beetle = self.beetle.take().expect("Trying to call run() after error");
let beetle = beetle.run(&mut self.state, self.memory.as_mut()).expect("Execute failed");
self.beetle = Some(beetle);
if self.registers_mut().a & 0xFF == 0x55 {
self.registers_mut().a >>= 8;
Some(self.pop())
} else {
None
}
}
pub fn is_aligned(addr: u32) -> bool {
addr & 0x3 == 0
}
}
impl std::fmt::Debug for VM {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
f.debug_struct("VM")
.field("state", &self.state)
.field("m0", &format!("{:#x}", self.memory().as_ptr() as u64))
.finish()
}
}
pub fn ackermann_object() -> Vec<u32> {
vec![
0x00001504, 0x00000245, 0x00002108, 0x00000843,
0x00001501, 0x00000345, 0x001A2202, 0xFFFFF849,
0x00000343, 0x22062204, 0xFFFFF549, 0xFFFFF449,
0x0000004A,
]
}
#[test]
pub fn halt() {
let mut vm = VM::new(MEMORY_CELLS, DATA_CELLS, RETURN_CELLS);
let initial_sp = vm.registers().sp;
let initial_rp = vm.registers().rp;
let entry_address = vm.halt_addr;
let exit = unsafe { vm.run(entry_address) };
assert_eq!(exit, Some(0));
assert_eq!(vm.registers().sp, initial_sp);
assert_eq!(vm.registers().rp, initial_rp);
}
#[test]
pub fn ackermann() {
let mut vm = VM::new(MEMORY_CELLS, DATA_CELLS, RETURN_CELLS);
vm.load_object(ackermann_object().as_ref());
let initial_sp = vm.registers().sp;
let initial_rp = vm.registers().rp;
vm.push(3);
vm.push(5);
vm.rpush(vm.halt_addr);
let exit = unsafe { vm.run(0) };
assert_eq!(exit, Some(0));
let result = vm.pop();
assert_eq!(vm.registers().sp, initial_sp);
assert_eq!(vm.registers().rp, initial_rp);
assert_eq!(result, 253);
}