821 lines
30 KiB
Rust
821 lines
30 KiB
Rust
use std::convert::TryFrom;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u16)]
|
|
pub enum OpCode {
|
|
// 6.1 Controle de Execução
|
|
Nop = 0x00,
|
|
Halt = 0x01,
|
|
Jmp = 0x02,
|
|
JmpIfFalse = 0x03,
|
|
|
|
// 6.2 Pilha
|
|
PushConst = 0x10,
|
|
Pop = 0x11,
|
|
Dup = 0x12,
|
|
Swap = 0x13,
|
|
|
|
// 6.3 Aritmética
|
|
Add = 0x20,
|
|
Sub = 0x21,
|
|
Mul = 0x22,
|
|
Div = 0x23,
|
|
|
|
// 6.4 Comparação e Lógica
|
|
Eq = 0x30,
|
|
Neq = 0x31,
|
|
Lt = 0x32,
|
|
Gt = 0x33,
|
|
And = 0x34,
|
|
Or = 0x35,
|
|
Not = 0x36,
|
|
|
|
// 6.5 Variáveis
|
|
GetGlobal = 0x40,
|
|
SetGlobal = 0x41,
|
|
GetLocal = 0x42,
|
|
SetLocal = 0x43,
|
|
|
|
// 6.6 Funções
|
|
Call = 0x50,
|
|
Ret = 0x51,
|
|
PushScope = 0x52,
|
|
PopScope = 0x53,
|
|
|
|
// 6.7 Heap
|
|
Alloc = 0x60,
|
|
LoadRef = 0x61,
|
|
StoreRef = 0x62,
|
|
|
|
// 6.8 Periféricos e Sistema
|
|
Syscall = 0x70,
|
|
FrameSync = 0x80,
|
|
}
|
|
|
|
impl TryFrom<u16> for OpCode {
|
|
type Error = String;
|
|
|
|
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0x00 => Ok(OpCode::Nop),
|
|
0x01 => Ok(OpCode::Halt),
|
|
0x02 => Ok(OpCode::Jmp),
|
|
0x03 => Ok(OpCode::JmpIfFalse),
|
|
0x10 => Ok(OpCode::PushConst),
|
|
0x11 => Ok(OpCode::Pop),
|
|
0x12 => Ok(OpCode::Dup),
|
|
0x13 => Ok(OpCode::Swap),
|
|
0x20 => Ok(OpCode::Add),
|
|
0x21 => Ok(OpCode::Sub),
|
|
0x22 => Ok(OpCode::Mul),
|
|
0x23 => Ok(OpCode::Div),
|
|
0x30 => Ok(OpCode::Eq),
|
|
0x31 => Ok(OpCode::Neq),
|
|
0x32 => Ok(OpCode::Lt),
|
|
0x33 => Ok(OpCode::Gt),
|
|
0x34 => Ok(OpCode::And),
|
|
0x35 => Ok(OpCode::Or),
|
|
0x36 => Ok(OpCode::Not),
|
|
0x40 => Ok(OpCode::GetGlobal),
|
|
0x41 => Ok(OpCode::SetGlobal),
|
|
0x42 => Ok(OpCode::GetLocal),
|
|
0x43 => Ok(OpCode::SetLocal),
|
|
0x50 => Ok(OpCode::Call),
|
|
0x51 => Ok(OpCode::Ret),
|
|
0x52 => Ok(OpCode::PushScope),
|
|
0x53 => Ok(OpCode::PopScope),
|
|
0x60 => Ok(OpCode::Alloc),
|
|
0x61 => Ok(OpCode::LoadRef),
|
|
0x62 => Ok(OpCode::StoreRef),
|
|
0x70 => Ok(OpCode::Syscall),
|
|
0x80 => Ok(OpCode::FrameSync),
|
|
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl OpCode {
|
|
pub fn cycles(&self) -> u64 {
|
|
match self {
|
|
OpCode::Nop => 1,
|
|
OpCode::Halt => 1,
|
|
OpCode::Jmp => 2,
|
|
OpCode::JmpIfFalse => 3,
|
|
OpCode::PushConst => 2,
|
|
OpCode::Pop => 1,
|
|
OpCode::Dup => 1,
|
|
OpCode::Swap => 1,
|
|
OpCode::Add => 2,
|
|
OpCode::Sub => 2,
|
|
OpCode::Mul => 4,
|
|
OpCode::Div => 6,
|
|
OpCode::Eq => 2,
|
|
OpCode::Neq => 2,
|
|
OpCode::Lt => 2,
|
|
OpCode::Gt => 2,
|
|
OpCode::And => 2,
|
|
OpCode::Or => 2,
|
|
OpCode::Not => 1,
|
|
OpCode::GetGlobal => 3,
|
|
OpCode::SetGlobal => 3,
|
|
OpCode::GetLocal => 2,
|
|
OpCode::SetLocal => 2,
|
|
OpCode::Call => 5,
|
|
OpCode::Ret => 4,
|
|
OpCode::PushScope => 3,
|
|
OpCode::PopScope => 3,
|
|
OpCode::Alloc => 10,
|
|
OpCode::LoadRef => 3,
|
|
OpCode::StoreRef => 3,
|
|
OpCode::Syscall => 1, // Variável, mas vamos usar 1 como base ou definir via ID
|
|
OpCode::FrameSync => 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Value {
|
|
Integer(i64),
|
|
Float(f64),
|
|
Boolean(bool),
|
|
String(String),
|
|
Ref(usize), // Referência ao heap
|
|
Null,
|
|
}
|
|
|
|
impl PartialEq for Value {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
match (self, other) {
|
|
(Value::Integer(a), Value::Integer(b)) => a == b,
|
|
(Value::Float(a), Value::Float(b)) => a == b,
|
|
(Value::Integer(a), Value::Float(b)) => *a as f64 == *b,
|
|
(Value::Float(a), Value::Integer(b)) => *a == *b as f64,
|
|
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
|
(Value::String(a), Value::String(b)) => a == b,
|
|
(Value::Ref(a), Value::Ref(b)) => a == b,
|
|
(Value::Null, Value::Null) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Value {
|
|
pub fn as_float(&self) -> Option<f64> {
|
|
match self {
|
|
Value::Integer(i) => Some(*i as f64),
|
|
Value::Float(f) => Some(*f),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn as_integer(&self) -> Option<i64> {
|
|
match self {
|
|
Value::Integer(i) => Some(*i),
|
|
Value::Float(f) => Some(*f as i64),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct CallFrame {
|
|
pub return_address: usize,
|
|
pub stack_base: usize,
|
|
pub locals_count: usize,
|
|
}
|
|
|
|
pub trait NativeInterface {
|
|
fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String>;
|
|
}
|
|
|
|
pub struct VirtualMachine {
|
|
pub pc: usize,
|
|
pub operand_stack: Vec<Value>,
|
|
pub call_stack: Vec<CallFrame>,
|
|
pub globals: Vec<Value>,
|
|
pub constant_pool: Vec<Value>,
|
|
pub rom: Vec<u8>,
|
|
pub heap: Vec<Value>, // Simplificado para demo, futuramente RAM/Heap real
|
|
pub cycles: u64,
|
|
pub halted: bool,
|
|
}
|
|
|
|
impl VirtualMachine {
|
|
pub fn new(rom: Vec<u8>, constant_pool: Vec<Value>) -> Self {
|
|
Self {
|
|
pc: 0,
|
|
operand_stack: Vec::new(),
|
|
call_stack: Vec::new(),
|
|
globals: Vec::new(),
|
|
constant_pool,
|
|
rom,
|
|
heap: Vec::new(),
|
|
cycles: 0,
|
|
halted: false,
|
|
}
|
|
}
|
|
|
|
/// Cria uma VM com um "Boot ROM" básico que exibe uma animação de padrão de teste.
|
|
/// Isso garante que a máquina sempre mostre algo ao ser ligada, mesmo sem cartucho.
|
|
pub fn new_boot_rom() -> Self {
|
|
let mut rom = Vec::new();
|
|
let mut constant_pool = Vec::new();
|
|
|
|
// Constantes do Boot ROM
|
|
constant_pool.push(Value::Integer(140)); // 0: x
|
|
constant_pool.push(Value::Integer(70)); // 1: y
|
|
constant_pool.push(Value::Integer(40)); // 2: largura
|
|
constant_pool.push(Value::Integer(40)); // 3: altura
|
|
constant_pool.push(Value::Integer(7)); // 4: cor rect (indigo)
|
|
constant_pool.push(Value::Integer(0)); // 5: sample_id (square)
|
|
constant_pool.push(Value::Integer(0)); // 6: voice_id
|
|
constant_pool.push(Value::Integer(255)); // 7: volume
|
|
constant_pool.push(Value::Integer(127)); // 8: pan (center)
|
|
constant_pool.push(Value::Float(1.0)); // 9: pitch
|
|
constant_pool.push(Value::Integer(0)); // 10: bg color (black)
|
|
constant_pool.push(Value::Integer(10)); // 11: button START
|
|
|
|
// -- PROGRAMA --
|
|
|
|
// 1. Toca o som de boot "plim" (uma vez)
|
|
// Push arguments for audio.play_sample(sample_id, voice_id, volume, pan, pitch)
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&5u32.to_le_bytes()); // sample_id
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&6u32.to_le_bytes()); // voice_id
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&7u32.to_le_bytes()); // volume
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&8u32.to_le_bytes()); // pan
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&9u32.to_le_bytes()); // pitch
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x3001u32.to_le_bytes());
|
|
|
|
let loop_start = rom.len() as u32;
|
|
|
|
// 2. Verifica Cartucho e Input para Boot
|
|
// system.has_cart?
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x0001u32.to_le_bytes());
|
|
|
|
let jmp_no_cart_idx = rom.len();
|
|
rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder
|
|
|
|
// Se tem cartucho, checa START
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&11u32.to_le_bytes()); // START button ID
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x2001u32.to_le_bytes()); // input.get_pad
|
|
|
|
let jmp_no_start_idx = rom.len();
|
|
rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes()); // placeholder
|
|
|
|
// Se tem cartucho E START, run_cart!
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x0002u32.to_le_bytes()); // system.run_cart
|
|
|
|
// Destino para quando não tem cartucho ou não apertou START
|
|
let skip_cart_addr = rom.len() as u32;
|
|
// Patch placeholders
|
|
let skip_bytes = skip_cart_addr.to_le_bytes();
|
|
rom[jmp_no_cart_idx+2..jmp_no_cart_idx+6].copy_from_slice(&skip_bytes);
|
|
rom[jmp_no_start_idx+2..jmp_no_start_idx+6].copy_from_slice(&skip_bytes);
|
|
|
|
// 3. Limpa a tela
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&10u32.to_le_bytes()); // bg color
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x1001u32.to_le_bytes());
|
|
|
|
// 4. Desenha o quadrado no centro
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes()); // x
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&1u32.to_le_bytes()); // y
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&2u32.to_le_bytes()); // w
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&3u32.to_le_bytes()); // h
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&4u32.to_le_bytes()); // color
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x1002u32.to_le_bytes());
|
|
|
|
// 4. Sincroniza frame
|
|
rom.extend_from_slice(&(OpCode::FrameSync as u16).to_le_bytes());
|
|
|
|
// 5. Loop infinito
|
|
rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes());
|
|
rom.extend_from_slice(&loop_start.to_le_bytes());
|
|
|
|
Self::new(rom, constant_pool)
|
|
}
|
|
}
|
|
|
|
impl Default for VirtualMachine {
|
|
fn default() -> Self {
|
|
Self::new_boot_rom()
|
|
}
|
|
}
|
|
|
|
impl VirtualMachine {
|
|
pub fn run_budget(&mut self, budget: u64, native: &mut dyn NativeInterface) -> Result<u64, String> {
|
|
let start_cycles = self.cycles;
|
|
let mut budget_used = 0;
|
|
|
|
while budget_used < budget && !self.halted && self.pc < self.rom.len() {
|
|
let pc_before = self.pc;
|
|
let cycles_before = self.cycles;
|
|
let opcode_val = self.peek_u16()?;
|
|
let opcode = OpCode::try_from(opcode_val)?;
|
|
|
|
if opcode == OpCode::FrameSync {
|
|
self.pc += 2;
|
|
self.cycles += OpCode::FrameSync.cycles();
|
|
break;
|
|
}
|
|
|
|
self.step(native)?;
|
|
|
|
// Garantir progresso para evitar loop infinito real (onde nem PC nem ciclos avançam)
|
|
if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
|
|
return Err(format!("VM stuck at PC 0x{:08X}", self.pc));
|
|
}
|
|
|
|
budget_used = self.cycles - start_cycles;
|
|
}
|
|
|
|
Ok(budget_used)
|
|
}
|
|
|
|
fn peek_u16(&self) -> Result<u16, String> {
|
|
if self.pc + 2 > self.rom.len() {
|
|
return Err("Unexpected end of ROM".into());
|
|
}
|
|
let bytes = [
|
|
self.rom[self.pc],
|
|
self.rom[self.pc + 1],
|
|
];
|
|
Ok(u16::from_le_bytes(bytes))
|
|
}
|
|
|
|
pub fn step(&mut self, native: &mut dyn NativeInterface) -> Result<(), String> {
|
|
if self.halted || self.pc >= self.rom.len() {
|
|
return Ok(());
|
|
}
|
|
|
|
let opcode_val = self.read_u16()?;
|
|
let opcode = OpCode::try_from(opcode_val)?;
|
|
|
|
match opcode {
|
|
OpCode::Nop => {}
|
|
OpCode::Halt => {
|
|
self.halted = true;
|
|
}
|
|
OpCode::Jmp => {
|
|
let addr = self.read_u32()? as usize;
|
|
self.pc = addr;
|
|
}
|
|
OpCode::JmpIfFalse => {
|
|
let addr = self.read_u32()? as usize;
|
|
let val = self.pop()?;
|
|
if let Value::Boolean(false) = val {
|
|
self.pc = addr;
|
|
}
|
|
}
|
|
OpCode::PushConst => {
|
|
let idx = self.read_u32()? as usize;
|
|
let val = self.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?;
|
|
self.push(val);
|
|
}
|
|
OpCode::Pop => {
|
|
self.pop()?;
|
|
}
|
|
OpCode::Dup => {
|
|
let val = self.peek()?.clone();
|
|
self.push(val);
|
|
}
|
|
OpCode::Swap => {
|
|
let a = self.pop()?;
|
|
let b = self.pop()?;
|
|
self.push(a);
|
|
self.push(b);
|
|
}
|
|
OpCode::Add => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_add(b))),
|
|
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a + b)),
|
|
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 + b)),
|
|
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a + b as f64)),
|
|
_ => Err("Invalid types for ADD".into()),
|
|
})?,
|
|
OpCode::Sub => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_sub(b))),
|
|
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a - b)),
|
|
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
|
|
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a - b as f64)),
|
|
_ => Err("Invalid types for SUB".into()),
|
|
})?,
|
|
OpCode::Mul => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Integer(a.wrapping_mul(b))),
|
|
(Value::Float(a), Value::Float(b)) => Ok(Value::Float(a * b)),
|
|
(Value::Integer(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
|
|
(Value::Float(a), Value::Integer(b)) => Ok(Value::Float(a * b as f64)),
|
|
_ => Err("Invalid types for MUL".into()),
|
|
})?,
|
|
OpCode::Div => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => {
|
|
if b == 0 { return Err("Division by zero".into()); }
|
|
Ok(Value::Integer(a / b))
|
|
}
|
|
(Value::Float(a), Value::Float(b)) => {
|
|
if b == 0.0 { return Err("Division by zero".into()); }
|
|
Ok(Value::Float(a / b))
|
|
}
|
|
(Value::Integer(a), Value::Float(b)) => {
|
|
if b == 0.0 { return Err("Division by zero".into()); }
|
|
Ok(Value::Float(a as f64 / b))
|
|
}
|
|
(Value::Float(a), Value::Integer(b)) => {
|
|
if b == 0 { return Err("Division by zero".into()); }
|
|
Ok(Value::Float(a / b as f64))
|
|
}
|
|
_ => Err("Invalid types for DIV".into()),
|
|
})?,
|
|
OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b)))?,
|
|
OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b)))?,
|
|
OpCode::Lt => self.binary_op(|a, b| {
|
|
match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a < b)),
|
|
(Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a < b)),
|
|
(Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) < b)),
|
|
(Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a < (b as f64))),
|
|
_ => Err("Invalid types for LT".into()),
|
|
}
|
|
})?,
|
|
OpCode::Gt => self.binary_op(|a, b| {
|
|
match (a, b) {
|
|
(Value::Integer(a), Value::Integer(b)) => Ok(Value::Boolean(a > b)),
|
|
(Value::Float(a), Value::Float(b)) => Ok(Value::Boolean(a > b)),
|
|
(Value::Integer(a), Value::Float(b)) => Ok(Value::Boolean((a as f64) > b)),
|
|
(Value::Float(a), Value::Integer(b)) => Ok(Value::Boolean(a > (b as f64))),
|
|
_ => Err("Invalid types for GT".into()),
|
|
}
|
|
})?,
|
|
OpCode::And => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)),
|
|
_ => Err("Invalid types for AND".into()),
|
|
})?,
|
|
OpCode::Or => self.binary_op(|a, b| match (a, b) {
|
|
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a || b)),
|
|
_ => Err("Invalid types for OR".into()),
|
|
})?,
|
|
OpCode::Not => {
|
|
let val = self.pop()?;
|
|
if let Value::Boolean(b) = val {
|
|
self.push(Value::Boolean(!b));
|
|
} else {
|
|
return Err("Invalid type for NOT".into());
|
|
}
|
|
}
|
|
OpCode::GetGlobal => {
|
|
let idx = self.read_u32()? as usize;
|
|
let val = self.globals.get(idx).cloned().ok_or("Invalid global index")?;
|
|
self.push(val);
|
|
}
|
|
OpCode::SetGlobal => {
|
|
let idx = self.read_u32()? as usize;
|
|
let val = self.pop()?;
|
|
if idx >= self.globals.len() {
|
|
self.globals.resize(idx + 1, Value::Null);
|
|
}
|
|
self.globals[idx] = val;
|
|
}
|
|
OpCode::GetLocal => {
|
|
let idx = self.read_u32()? as usize;
|
|
let frame = self.call_stack.last().ok_or("No active call frame")?;
|
|
let val = self.operand_stack.get(frame.stack_base + idx).cloned().ok_or("Invalid local index")?;
|
|
self.push(val);
|
|
}
|
|
OpCode::SetLocal => {
|
|
let idx = self.read_u32()? as usize;
|
|
let val = self.pop()?;
|
|
let frame = self.call_stack.last().ok_or("No active call frame")?;
|
|
let stack_idx = frame.stack_base + idx;
|
|
if stack_idx >= self.operand_stack.len() {
|
|
return Err("Local index out of bounds".into());
|
|
}
|
|
self.operand_stack[stack_idx] = val;
|
|
}
|
|
OpCode::Call => {
|
|
let addr = self.read_u32()? as usize;
|
|
let args_count = self.read_u32()? as usize;
|
|
let stack_base = self.operand_stack.len() - args_count;
|
|
self.call_stack.push(CallFrame {
|
|
return_address: self.pc,
|
|
stack_base,
|
|
locals_count: args_count,
|
|
});
|
|
self.pc = addr;
|
|
}
|
|
OpCode::Ret => {
|
|
let frame = self.call_stack.pop().ok_or("Call stack underflow")?;
|
|
let return_val = self.pop()?;
|
|
self.operand_stack.truncate(frame.stack_base);
|
|
self.push(return_val);
|
|
self.pc = frame.return_address;
|
|
}
|
|
OpCode::PushScope => {
|
|
let locals_count = self.read_u32()? as usize;
|
|
let stack_base = self.operand_stack.len();
|
|
for _ in 0..locals_count {
|
|
self.push(Value::Null);
|
|
}
|
|
self.call_stack.push(CallFrame {
|
|
return_address: 0,
|
|
stack_base,
|
|
locals_count,
|
|
});
|
|
}
|
|
OpCode::PopScope => {
|
|
let frame = self.call_stack.pop().ok_or("Call stack underflow")?;
|
|
self.operand_stack.truncate(frame.stack_base);
|
|
}
|
|
OpCode::Alloc => {
|
|
let size = self.read_u32()? as usize;
|
|
let ref_idx = self.heap.len();
|
|
for _ in 0..size {
|
|
self.heap.push(Value::Null);
|
|
}
|
|
self.push(Value::Ref(ref_idx));
|
|
}
|
|
OpCode::LoadRef => {
|
|
let offset = self.read_u32()? as usize;
|
|
let ref_val = self.pop()?;
|
|
if let Value::Ref(base) = ref_val {
|
|
let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?;
|
|
self.push(val);
|
|
} else {
|
|
return Err("Expected reference for LOAD_REF".into());
|
|
}
|
|
}
|
|
OpCode::StoreRef => {
|
|
let offset = self.read_u32()? as usize;
|
|
let val = self.pop()?;
|
|
let ref_val = self.pop()?;
|
|
if let Value::Ref(base) = ref_val {
|
|
if base + offset >= self.heap.len() {
|
|
return Err("Invalid heap access".into());
|
|
}
|
|
self.heap[base + offset] = val;
|
|
} else {
|
|
return Err("Expected reference for STORE_REF".into());
|
|
}
|
|
}
|
|
OpCode::Syscall => {
|
|
let id = self.read_u32()?;
|
|
let native_cycles = native.call(id, self).map_err(|e| format!("Native call 0x{:08X} failed: {}", id, e))?;
|
|
self.cycles += native_cycles;
|
|
}
|
|
OpCode::FrameSync => {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
self.cycles += opcode.cycles();
|
|
Ok(())
|
|
}
|
|
|
|
fn read_u32(&mut self) -> Result<u32, String> {
|
|
if self.pc + 4 > self.rom.len() {
|
|
return Err("Unexpected end of ROM".into());
|
|
}
|
|
let bytes = [
|
|
self.rom[self.pc],
|
|
self.rom[self.pc + 1],
|
|
self.rom[self.pc + 2],
|
|
self.rom[self.pc + 3],
|
|
];
|
|
self.pc += 4;
|
|
Ok(u32::from_le_bytes(bytes))
|
|
}
|
|
|
|
fn read_u16(&mut self) -> Result<u16, String> {
|
|
if self.pc + 2 > self.rom.len() {
|
|
return Err("Unexpected end of ROM".into());
|
|
}
|
|
let bytes = [
|
|
self.rom[self.pc],
|
|
self.rom[self.pc + 1],
|
|
];
|
|
self.pc += 2;
|
|
Ok(u16::from_le_bytes(bytes))
|
|
}
|
|
|
|
pub fn push(&mut self, val: Value) {
|
|
self.operand_stack.push(val);
|
|
}
|
|
|
|
pub fn pop(&mut self) -> Result<Value, String> {
|
|
self.operand_stack.pop().ok_or("Stack underflow".into())
|
|
}
|
|
|
|
pub fn pop_number(&mut self) -> Result<f64, String> {
|
|
let val = self.pop()?;
|
|
val.as_float().ok_or_else(|| "Expected number".into())
|
|
}
|
|
|
|
pub fn pop_integer(&mut self) -> Result<i64, String> {
|
|
let val = self.pop()?;
|
|
val.as_integer().ok_or_else(|| "Expected integer".into())
|
|
}
|
|
|
|
pub fn peek(&self) -> Result<&Value, String> {
|
|
self.operand_stack.last().ok_or("Stack underflow".into())
|
|
}
|
|
|
|
fn binary_op<F>(&mut self, f: F) -> Result<(), String>
|
|
where
|
|
F: FnOnce(Value, Value) -> Result<Value, String>,
|
|
{
|
|
let b = self.pop()?;
|
|
let a = self.pop()?;
|
|
let res = f(a, b)?;
|
|
self.push(res);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_add_instructions() {
|
|
struct NoopNative;
|
|
impl NativeInterface for NoopNative {
|
|
fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
}
|
|
let mut native = NoopNative;
|
|
|
|
let mut rom = Vec::new();
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes()); // Const index 0
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&1u32.to_le_bytes()); // Const index 1
|
|
rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
|
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
|
|
|
let constant_pool = vec![Value::Integer(10), Value::Integer(20)];
|
|
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
|
|
vm.run_budget(100, &mut native).unwrap();
|
|
|
|
assert_eq!(vm.operand_stack.len(), 1);
|
|
assert_eq!(vm.operand_stack[0], Value::Integer(30));
|
|
assert!(vm.halted);
|
|
}
|
|
|
|
#[test]
|
|
fn test_jump_and_loop() {
|
|
struct NoopNative;
|
|
impl NativeInterface for NoopNative {
|
|
fn call(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
}
|
|
let mut native = NoopNative;
|
|
|
|
let mut rom = Vec::new();
|
|
// Index 0: PUSH 0 (counter)
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes());
|
|
|
|
// Index 6: DUP
|
|
let loop_start = rom.len();
|
|
rom.extend_from_slice(&(OpCode::Dup as u16).to_le_bytes());
|
|
|
|
// PUSH 10
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&1u32.to_le_bytes());
|
|
|
|
// LT (counter < 10)
|
|
rom.extend_from_slice(&(OpCode::Lt as u16).to_le_bytes());
|
|
|
|
// JMP_IF_FALSE to end
|
|
rom.extend_from_slice(&(OpCode::JmpIfFalse as u16).to_le_bytes());
|
|
let jmp_placeholder = rom.len();
|
|
rom.extend_from_slice(&0u32.to_le_bytes());
|
|
|
|
// PUSH 1
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&2u32.to_le_bytes());
|
|
|
|
// ADD (increment counter)
|
|
rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
|
|
|
|
// JMP to start
|
|
rom.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes());
|
|
rom.extend_from_slice(&(loop_start as u32).to_le_bytes());
|
|
|
|
// End
|
|
let loop_end = rom.len();
|
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
|
|
|
// Patch JMP_IF_FALSE addr
|
|
let end_addr_bytes = (loop_end as u32).to_le_bytes();
|
|
rom[jmp_placeholder..jmp_placeholder+4].copy_from_slice(&end_addr_bytes);
|
|
|
|
let constant_pool = vec![Value::Integer(0), Value::Integer(10), Value::Integer(1)];
|
|
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
|
|
vm.run_budget(1000, &mut native).unwrap();
|
|
|
|
assert!(vm.halted);
|
|
// O valor final na pilha deve ser 10
|
|
assert_eq!(vm.operand_stack.last(), Some(&Value::Integer(10)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_native_call_gfx_clear() {
|
|
struct MockGfx {
|
|
cleared_color: Option<usize>,
|
|
}
|
|
impl NativeInterface for MockGfx {
|
|
fn call(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String> {
|
|
if id == 0x1001 {
|
|
let color = vm.pop_integer()? as usize;
|
|
self.cleared_color = Some(color);
|
|
return Ok(100);
|
|
}
|
|
Ok(0)
|
|
}
|
|
}
|
|
let mut native = MockGfx { cleared_color: None };
|
|
|
|
let mut rom = Vec::new();
|
|
// PUSH 5 (color index)
|
|
rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0u32.to_le_bytes());
|
|
// CALL_NATIVE 0x1001
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x1001u32.to_le_bytes());
|
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
|
|
|
let constant_pool = vec![Value::Integer(5)];
|
|
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
|
|
vm.run_budget(1000, &mut native).unwrap();
|
|
|
|
assert!(vm.halted);
|
|
assert_eq!(native.cleared_color, Some(5));
|
|
}
|
|
|
|
#[test]
|
|
fn test_system_run_cart() {
|
|
use crate::Machine;
|
|
use crate::model::Cartridge;
|
|
|
|
let mut machine = Machine::new();
|
|
|
|
// 1. Verifica que não tem cartucho inicialmente
|
|
let mut rom = Vec::new();
|
|
// CALL_NATIVE 0x0001 (has_cart)
|
|
rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
rom.extend_from_slice(&0x0001u32.to_le_bytes());
|
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
|
|
|
machine.vm = VirtualMachine::new(rom, vec![]);
|
|
let mut vm = std::mem::take(&mut machine.vm);
|
|
vm.run_budget(100, &mut machine).unwrap();
|
|
machine.vm = vm;
|
|
|
|
assert_eq!(machine.vm.pop().unwrap(), Value::Boolean(false));
|
|
|
|
// 2. Adiciona um cartucho e roda
|
|
let mut cart_rom = Vec::new();
|
|
// PUSH_CONST 0
|
|
cart_rom.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
|
|
cart_rom.extend_from_slice(&0u32.to_le_bytes());
|
|
cart_rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
|
let cart_pool = vec![Value::Integer(42)];
|
|
|
|
let cart = Cartridge::new(cart_rom, cart_pool);
|
|
machine.load_cartridge(cart);
|
|
|
|
// Código para rodar o cartucho
|
|
let mut boot_rom = Vec::new();
|
|
// CALL_NATIVE 0x0002 (run_cart)
|
|
boot_rom.extend_from_slice(&(OpCode::Syscall as u16).to_le_bytes());
|
|
boot_rom.extend_from_slice(&0x0002u32.to_le_bytes());
|
|
|
|
machine.vm = VirtualMachine::new(boot_rom, vec![]);
|
|
let mut vm = std::mem::take(&mut machine.vm);
|
|
vm.run_budget(1000, &mut machine).unwrap();
|
|
machine.vm = vm;
|
|
|
|
// Após o run_budget, a VM deve ter executado o cartucho
|
|
assert_eq!(machine.vm.operand_stack.last(), Some(&Value::Integer(42)));
|
|
assert!(machine.vm.halted);
|
|
}
|
|
}
|