dev/vm-improvements #3
@ -8,6 +8,7 @@ pub enum OpCode {
|
|||||||
Jmp = 0x02,
|
Jmp = 0x02,
|
||||||
JmpIfFalse = 0x03,
|
JmpIfFalse = 0x03,
|
||||||
JmpIfTrue = 0x04,
|
JmpIfTrue = 0x04,
|
||||||
|
Trap = 0x05,
|
||||||
|
|
||||||
// 6.2 Stack
|
// 6.2 Stack
|
||||||
PushConst = 0x10,
|
PushConst = 0x10,
|
||||||
@ -18,6 +19,7 @@ pub enum OpCode {
|
|||||||
PushF64 = 0x15,
|
PushF64 = 0x15,
|
||||||
PushBool = 0x16,
|
PushBool = 0x16,
|
||||||
PushI32 = 0x17,
|
PushI32 = 0x17,
|
||||||
|
PopN = 0x18,
|
||||||
|
|
||||||
// 6.3 Arithmetic
|
// 6.3 Arithmetic
|
||||||
Add = 0x20,
|
Add = 0x20,
|
||||||
@ -74,6 +76,7 @@ impl TryFrom<u16> for OpCode {
|
|||||||
0x02 => Ok(OpCode::Jmp),
|
0x02 => Ok(OpCode::Jmp),
|
||||||
0x03 => Ok(OpCode::JmpIfFalse),
|
0x03 => Ok(OpCode::JmpIfFalse),
|
||||||
0x04 => Ok(OpCode::JmpIfTrue),
|
0x04 => Ok(OpCode::JmpIfTrue),
|
||||||
|
0x05 => Ok(OpCode::Trap),
|
||||||
0x10 => Ok(OpCode::PushConst),
|
0x10 => Ok(OpCode::PushConst),
|
||||||
0x11 => Ok(OpCode::Pop),
|
0x11 => Ok(OpCode::Pop),
|
||||||
0x12 => Ok(OpCode::Dup),
|
0x12 => Ok(OpCode::Dup),
|
||||||
@ -82,6 +85,7 @@ impl TryFrom<u16> for OpCode {
|
|||||||
0x15 => Ok(OpCode::PushF64),
|
0x15 => Ok(OpCode::PushF64),
|
||||||
0x16 => Ok(OpCode::PushBool),
|
0x16 => Ok(OpCode::PushBool),
|
||||||
0x17 => Ok(OpCode::PushI32),
|
0x17 => Ok(OpCode::PushI32),
|
||||||
|
0x18 => Ok(OpCode::PopN),
|
||||||
0x20 => Ok(OpCode::Add),
|
0x20 => Ok(OpCode::Add),
|
||||||
0x21 => Ok(OpCode::Sub),
|
0x21 => Ok(OpCode::Sub),
|
||||||
0x22 => Ok(OpCode::Mul),
|
0x22 => Ok(OpCode::Mul),
|
||||||
@ -127,8 +131,10 @@ impl OpCode {
|
|||||||
OpCode::Jmp => 2,
|
OpCode::Jmp => 2,
|
||||||
OpCode::JmpIfFalse => 3,
|
OpCode::JmpIfFalse => 3,
|
||||||
OpCode::JmpIfTrue => 3,
|
OpCode::JmpIfTrue => 3,
|
||||||
|
OpCode::Trap => 1,
|
||||||
OpCode::PushConst => 2,
|
OpCode::PushConst => 2,
|
||||||
OpCode::Pop => 1,
|
OpCode::Pop => 1,
|
||||||
|
OpCode::PopN => 2,
|
||||||
OpCode::Dup => 1,
|
OpCode::Dup => 1,
|
||||||
OpCode::Swap => 1,
|
OpCode::Swap => 1,
|
||||||
OpCode::PushI64 => 2,
|
OpCode::PushI64 => 2,
|
||||||
@ -178,7 +184,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_opcode_decoding() {
|
fn test_opcode_decoding() {
|
||||||
assert_eq!(OpCode::try_from(0x00).unwrap(), OpCode::Nop);
|
assert_eq!(OpCode::try_from(0x00).unwrap(), OpCode::Nop);
|
||||||
|
assert_eq!(OpCode::try_from(0x05).unwrap(), OpCode::Trap);
|
||||||
assert_eq!(OpCode::try_from(0x10).unwrap(), OpCode::PushConst);
|
assert_eq!(OpCode::try_from(0x10).unwrap(), OpCode::PushConst);
|
||||||
|
assert_eq!(OpCode::try_from(0x18).unwrap(), OpCode::PopN);
|
||||||
assert_eq!(OpCode::try_from(0x20).unwrap(), OpCode::Add);
|
assert_eq!(OpCode::try_from(0x20).unwrap(), OpCode::Add);
|
||||||
assert_eq!(OpCode::try_from(0x70).unwrap(), OpCode::Syscall);
|
assert_eq!(OpCode::try_from(0x70).unwrap(), OpCode::Syscall);
|
||||||
assert_eq!(OpCode::try_from(0x80).unwrap(), OpCode::FrameSync);
|
assert_eq!(OpCode::try_from(0x80).unwrap(), OpCode::FrameSync);
|
||||||
|
|||||||
@ -243,6 +243,14 @@ impl VirtualMachine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opcode == OpCode::Trap {
|
||||||
|
self.pc += 2; // Advance PC past the opcode
|
||||||
|
self.cycles += OpCode::Trap.cycles();
|
||||||
|
steps_executed += 1;
|
||||||
|
ending_reason = Some(LogicalFrameEndingReason::Breakpoint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Execute a single step (Fetch-Decode-Execute)
|
// Execute a single step (Fetch-Decode-Execute)
|
||||||
self.step(native, hw)?;
|
self.step(native, hw)?;
|
||||||
steps_executed += 1;
|
steps_executed += 1;
|
||||||
@ -323,6 +331,10 @@ impl VirtualMachine {
|
|||||||
self.pc = addr;
|
self.pc = addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
OpCode::Trap => {
|
||||||
|
// Handled in run_budget for interruption,
|
||||||
|
// but we need to advance PC if executed via step() directly.
|
||||||
|
}
|
||||||
OpCode::PushConst => {
|
OpCode::PushConst => {
|
||||||
let idx = self.read_u32()? as usize;
|
let idx = self.read_u32()? as usize;
|
||||||
let val = self.program.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?;
|
let val = self.program.constant_pool.get(idx).cloned().ok_or("Invalid constant index")?;
|
||||||
@ -347,6 +359,12 @@ impl VirtualMachine {
|
|||||||
OpCode::Pop => {
|
OpCode::Pop => {
|
||||||
self.pop()?;
|
self.pop()?;
|
||||||
}
|
}
|
||||||
|
OpCode::PopN => {
|
||||||
|
let n = self.read_u16()?;
|
||||||
|
for _ in 0..n {
|
||||||
|
self.pop()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
OpCode::Dup => {
|
OpCode::Dup => {
|
||||||
let val = self.peek()?.clone();
|
let val = self.peek()?.clone();
|
||||||
self.push(val);
|
self.push(val);
|
||||||
@ -1180,4 +1198,46 @@ mod tests {
|
|||||||
vm.step(&mut native, &mut hw).unwrap(); // PushI32
|
vm.step(&mut native, &mut hw).unwrap(); // PushI32
|
||||||
assert_eq!(vm.pop().unwrap(), Value::Int32(100));
|
assert_eq!(vm.pop().unwrap(), Value::Int32(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trap_opcode() {
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut hw = MockHardware;
|
||||||
|
|
||||||
|
let mut rom = Vec::new();
|
||||||
|
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&42i32.to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::Trap as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||||
|
|
||||||
|
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||||
|
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(report.reason, LogicalFrameEndingReason::Breakpoint);
|
||||||
|
assert_eq!(vm.pc, 8); // PushI32 (6 bytes) + Trap (2 bytes)
|
||||||
|
assert_eq!(vm.peek().unwrap(), &Value::Int32(42));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pop_n_opcode() {
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut hw = MockHardware;
|
||||||
|
|
||||||
|
let mut rom = Vec::new();
|
||||||
|
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&1i32.to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&2i32.to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&3i32.to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::PopN as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&2u16.to_le_bytes());
|
||||||
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||||
|
|
||||||
|
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||||
|
vm.run_budget(100, &mut native, &mut hw).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(vm.pop().unwrap(), Value::Int32(1));
|
||||||
|
assert!(vm.pop().is_err()); // Stack should be empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user