394 lines
13 KiB
Rust
394 lines
13 KiB
Rust
/// Represents a single instruction in the Prometeu Virtual Machine.
|
|
///
|
|
/// Each OpCode is encoded as a 16-bit unsigned integer (u16) in the bytecode.
|
|
/// The PVM is a stack-based machine, meaning most instructions take their
|
|
/// operands from the top of the stack and push their results back onto it.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[repr(u16)]
|
|
pub enum OpCode {
|
|
// --- 6.1 Execution Control ---
|
|
|
|
/// No operation. Does nothing for 1 cycle.
|
|
Nop = 0x00,
|
|
/// Stops the Virtual Machine execution immediately.
|
|
Halt = 0x01,
|
|
/// Unconditional jump to a specific PC (Program Counter) address.
|
|
/// Operand: addr (u32)
|
|
Jmp = 0x02,
|
|
/// Jumps to `addr` if the value at the top of the stack is `false`.
|
|
/// Operand: addr (u32)
|
|
/// Stack: [bool] -> []
|
|
JmpIfFalse = 0x03,
|
|
/// Jumps to `addr` if the value at the top of the stack is `true`.
|
|
/// Operand: addr (u32)
|
|
/// Stack: [bool] -> []
|
|
JmpIfTrue = 0x04,
|
|
/// Triggers a software breakpoint. Used for debugging.
|
|
Trap = 0x05,
|
|
|
|
// --- 6.2 Stack Manipulation ---
|
|
|
|
/// Loads a constant from the Constant Pool into the stack.
|
|
/// Operand: index (u32)
|
|
/// Stack: [] -> [value]
|
|
PushConst = 0x10,
|
|
/// Removes the top value from the stack.
|
|
/// Stack: [val] -> []
|
|
Pop = 0x11,
|
|
/// Duplicates the top value of the stack.
|
|
/// Stack: [val] -> [val, val]
|
|
Dup = 0x12,
|
|
/// Swaps the two top values of the stack.
|
|
/// Stack: [a, b] -> [b, a]
|
|
Swap = 0x13,
|
|
/// Pushes a 64-bit integer literal onto the stack.
|
|
/// Operand: value (i64)
|
|
PushI64 = 0x14,
|
|
/// Pushes a 64-bit float literal onto the stack.
|
|
/// Operand: value (f64)
|
|
PushF64 = 0x15,
|
|
/// Pushes a boolean literal onto the stack (0=false, 1=true).
|
|
/// Operand: value (u8)
|
|
PushBool = 0x16,
|
|
/// Pushes a 32-bit integer literal onto the stack.
|
|
/// Operand: value (i32)
|
|
PushI32 = 0x17,
|
|
/// Removes `n` values from the stack.
|
|
/// Operand: n (u32)
|
|
PopN = 0x18,
|
|
|
|
// --- 6.3 Arithmetic ---
|
|
|
|
/// Adds the two top values (a + b).
|
|
/// Stack: [a, b] -> [result]
|
|
Add = 0x20,
|
|
/// Subtracts the top value from the second one (a - b).
|
|
/// Stack: [a, b] -> [result]
|
|
Sub = 0x21,
|
|
/// Multiplies the two top values (a * b).
|
|
/// Stack: [a, b] -> [result]
|
|
Mul = 0x22,
|
|
/// Divides the second top value by the top one (a / b).
|
|
/// Stack: [a, b] -> [result]
|
|
Div = 0x23,
|
|
|
|
// --- 6.4 Comparison and Logic ---
|
|
|
|
/// Checks if a equals b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Eq = 0x30,
|
|
/// Checks if a is not equal to b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Neq = 0x31,
|
|
/// Checks if a is less than b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Lt = 0x32,
|
|
/// Checks if a is greater than b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Gt = 0x33,
|
|
/// Logical AND.
|
|
/// Stack: [bool, bool] -> [bool]
|
|
And = 0x34,
|
|
/// Logical OR.
|
|
/// Stack: [bool, bool] -> [bool]
|
|
Or = 0x35,
|
|
/// Logical NOT.
|
|
/// Stack: [bool] -> [bool]
|
|
Not = 0x36,
|
|
/// Bitwise AND.
|
|
/// Stack: [int, int] -> [int]
|
|
BitAnd = 0x37,
|
|
/// Bitwise OR.
|
|
/// Stack: [int, int] -> [int]
|
|
BitOr = 0x38,
|
|
/// Bitwise XOR.
|
|
/// Stack: [int, int] -> [int]
|
|
BitXor = 0x39,
|
|
/// Bitwise Shift Left.
|
|
/// Stack: [int, count] -> [int]
|
|
Shl = 0x3A,
|
|
/// Bitwise Shift Right.
|
|
/// Stack: [int, count] -> [int]
|
|
Shr = 0x3B,
|
|
/// Checks if a is less than or equal to b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Lte = 0x3C,
|
|
/// Checks if a is greater than or equal to b.
|
|
/// Stack: [a, b] -> [bool]
|
|
Gte = 0x3D,
|
|
/// Negates a number (-a).
|
|
/// Stack: [num] -> [num]
|
|
Neg = 0x3E,
|
|
|
|
// --- 6.5 Variables ---
|
|
|
|
/// Loads a value from a global variable slot.
|
|
/// Operand: slot_index (u32)
|
|
/// Stack: [] -> [value]
|
|
GetGlobal = 0x40,
|
|
/// Stores the top value into a global variable slot.
|
|
/// Operand: slot_index (u32)
|
|
/// Stack: [value] -> []
|
|
SetGlobal = 0x41,
|
|
/// Loads a value from a local variable slot in the current frame.
|
|
/// Operand: slot_index (u32)
|
|
/// Stack: [] -> [value]
|
|
GetLocal = 0x42,
|
|
/// Stores the top value into a local variable slot in the current frame.
|
|
/// Operand: slot_index (u32)
|
|
/// Stack: [value] -> []
|
|
SetLocal = 0x43,
|
|
|
|
// --- 6.6 Functions ---
|
|
|
|
/// Calls a function at a specific address.
|
|
/// Operands: addr (u32), args_count (u32)
|
|
/// Stack: [arg0, arg1, ...] -> [return_value]
|
|
Call = 0x50,
|
|
/// Returns from the current function.
|
|
/// Stack: [return_val] -> [return_val]
|
|
Ret = 0x51,
|
|
/// Starts a new local scope (for blocks/loops).
|
|
PushScope = 0x52,
|
|
/// Ends the current local scope, discarding its local variables.
|
|
PopScope = 0x53,
|
|
|
|
// --- 6.7 HIP (Heap Interface Protocol) ---
|
|
|
|
/// Allocates `slots` slots on the heap with the given `type_id`.
|
|
/// Operands: type_id (u32), slots (u32)
|
|
/// Stack: [] -> [gate]
|
|
Alloc = 0x60,
|
|
/// Reads a value from the heap at `gate + offset`.
|
|
/// Operand: offset (u32)
|
|
/// Stack: [gate] -> [value]
|
|
GateLoad = 0x61,
|
|
/// Writes a value to the heap at `gate + offset`.
|
|
/// Operand: offset (u32)
|
|
/// Stack: [gate, value] -> []
|
|
GateStore = 0x62,
|
|
|
|
/// Marks the beginning of a Peek scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateBeginPeek = 0x63,
|
|
/// Marks the end of a Peek scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateEndPeek = 0x64,
|
|
/// Marks the beginning of a Borrow scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateBeginBorrow = 0x65,
|
|
/// Marks the end of a Borrow scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateEndBorrow = 0x66,
|
|
/// Marks the beginning of a Mutate scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateBeginMutate = 0x67,
|
|
/// Marks the end of a Mutate scope for a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateEndMutate = 0x68,
|
|
|
|
/// Increments the reference count of a gate.
|
|
/// Stack: [gate] -> [gate]
|
|
GateRetain = 0x69,
|
|
/// Decrements the reference count of a gate.
|
|
/// Stack: [gate] -> []
|
|
GateRelease = 0x6A,
|
|
|
|
// --- 6.8 Peripherals and System ---
|
|
|
|
/// Invokes a system function (Firmware/OS).
|
|
/// Operand: syscall_id (u32)
|
|
/// Stack: [args...] -> [results...] (depends on syscall)
|
|
Syscall = 0x70,
|
|
/// Synchronizes the VM with the hardware frame (usually 60Hz).
|
|
/// Execution pauses until the next VSync.
|
|
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),
|
|
0x04 => Ok(OpCode::JmpIfTrue),
|
|
0x05 => Ok(OpCode::Trap),
|
|
0x10 => Ok(OpCode::PushConst),
|
|
0x11 => Ok(OpCode::Pop),
|
|
0x12 => Ok(OpCode::Dup),
|
|
0x13 => Ok(OpCode::Swap),
|
|
0x14 => Ok(OpCode::PushI64),
|
|
0x15 => Ok(OpCode::PushF64),
|
|
0x16 => Ok(OpCode::PushBool),
|
|
0x17 => Ok(OpCode::PushI32),
|
|
0x18 => Ok(OpCode::PopN),
|
|
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),
|
|
0x37 => Ok(OpCode::BitAnd),
|
|
0x38 => Ok(OpCode::BitOr),
|
|
0x39 => Ok(OpCode::BitXor),
|
|
0x3A => Ok(OpCode::Shl),
|
|
0x3B => Ok(OpCode::Shr),
|
|
0x3C => Ok(OpCode::Lte),
|
|
0x3D => Ok(OpCode::Gte),
|
|
0x3E => Ok(OpCode::Neg),
|
|
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::GateLoad),
|
|
0x62 => Ok(OpCode::GateStore),
|
|
0x63 => Ok(OpCode::GateBeginPeek),
|
|
0x64 => Ok(OpCode::GateEndPeek),
|
|
0x65 => Ok(OpCode::GateBeginBorrow),
|
|
0x66 => Ok(OpCode::GateEndBorrow),
|
|
0x67 => Ok(OpCode::GateBeginMutate),
|
|
0x68 => Ok(OpCode::GateEndMutate),
|
|
0x69 => Ok(OpCode::GateRetain),
|
|
0x6A => Ok(OpCode::GateRelease),
|
|
0x70 => Ok(OpCode::Syscall),
|
|
0x80 => Ok(OpCode::FrameSync),
|
|
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl OpCode {
|
|
/// Returns the cost of the instruction in VM cycles.
|
|
/// This is used for performance monitoring and resource limiting (Certification).
|
|
pub fn cycles(&self) -> u64 {
|
|
match self {
|
|
OpCode::Nop => 1,
|
|
OpCode::Halt => 1,
|
|
OpCode::Jmp => 2,
|
|
OpCode::JmpIfFalse => 3,
|
|
OpCode::JmpIfTrue => 3,
|
|
OpCode::Trap => 1,
|
|
OpCode::PushConst => 2,
|
|
OpCode::Pop => 1,
|
|
OpCode::PopN => 2,
|
|
OpCode::Dup => 1,
|
|
OpCode::Swap => 1,
|
|
OpCode::PushI64 => 2,
|
|
OpCode::PushF64 => 2,
|
|
OpCode::PushBool => 2,
|
|
OpCode::PushI32 => 2,
|
|
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::BitAnd => 2,
|
|
OpCode::BitOr => 2,
|
|
OpCode::BitXor => 2,
|
|
OpCode::Shl => 2,
|
|
OpCode::Shr => 2,
|
|
OpCode::Lte => 2,
|
|
OpCode::Gte => 2,
|
|
OpCode::Neg => 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::GateLoad => 3,
|
|
OpCode::GateStore => 3,
|
|
OpCode::GateBeginPeek => 1,
|
|
OpCode::GateEndPeek => 1,
|
|
OpCode::GateBeginBorrow => 1,
|
|
OpCode::GateEndBorrow => 1,
|
|
OpCode::GateBeginMutate => 1,
|
|
OpCode::GateEndMutate => 1,
|
|
OpCode::GateRetain => 1,
|
|
OpCode::GateRelease => 1,
|
|
OpCode::Syscall => 1,
|
|
OpCode::FrameSync => 1,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::asm::{assemble, Asm, Operand};
|
|
|
|
#[test]
|
|
fn test_opcode_stability() {
|
|
// Normative test: ensures opcode numeric values are frozen.
|
|
assert_eq!(OpCode::Nop as u16, 0x00);
|
|
assert_eq!(OpCode::PushConst as u16, 0x10);
|
|
assert_eq!(OpCode::Alloc as u16, 0x60);
|
|
assert_eq!(OpCode::GateLoad as u16, 0x61);
|
|
assert_eq!(OpCode::GateStore as u16, 0x62);
|
|
assert_eq!(OpCode::GateBeginPeek as u16, 0x63);
|
|
assert_eq!(OpCode::GateEndPeek as u16, 0x64);
|
|
assert_eq!(OpCode::GateBeginBorrow as u16, 0x65);
|
|
assert_eq!(OpCode::GateEndBorrow as u16, 0x66);
|
|
assert_eq!(OpCode::GateBeginMutate as u16, 0x67);
|
|
assert_eq!(OpCode::GateEndMutate as u16, 0x68);
|
|
assert_eq!(OpCode::GateRetain as u16, 0x69);
|
|
assert_eq!(OpCode::GateRelease as u16, 0x6A);
|
|
assert_eq!(OpCode::FrameSync as u16, 0x80);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_bytecode_golden() {
|
|
// Golden test for HIP opcodes and their encodings.
|
|
// Rule: All multi-byte operands are little-endian.
|
|
|
|
let instructions = vec![
|
|
Asm::Op(OpCode::Alloc, vec![Operand::U32(0x11223344), Operand::U32(0x55667788)]),
|
|
Asm::Op(OpCode::GateLoad, vec![Operand::U32(0xAABBCCDD)]),
|
|
Asm::Op(OpCode::GateStore, vec![Operand::U32(0x11223344)]),
|
|
Asm::Op(OpCode::GateBeginPeek, vec![]),
|
|
Asm::Op(OpCode::GateRetain, vec![]),
|
|
Asm::Op(OpCode::GateRelease, vec![]),
|
|
];
|
|
|
|
let bytes = assemble(&instructions).unwrap();
|
|
|
|
let mut expected = Vec::new();
|
|
// Alloc (0x60, 0x00) + type_id (44 33 22 11) + slots (88 77 66 55)
|
|
expected.extend_from_slice(&[0x60, 0x00, 0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55]);
|
|
// GateLoad (0x61, 0x00) + offset (DD CC BB AA)
|
|
expected.extend_from_slice(&[0x61, 0x00, 0xDD, 0xCC, 0xBB, 0xAA]);
|
|
// GateStore (0x62, 0x00) + offset (44 33 22 11)
|
|
expected.extend_from_slice(&[0x62, 0x00, 0x44, 0x33, 0x22, 0x11]);
|
|
// GateBeginPeek (0x63, 0x00)
|
|
expected.extend_from_slice(&[0x63, 0x00]);
|
|
// GateRetain (0x69, 0x00)
|
|
expected.extend_from_slice(&[0x69, 0x00]);
|
|
// GateRelease (0x6A, 0x00)
|
|
expected.extend_from_slice(&[0x6A, 0x00]);
|
|
|
|
assert_eq!(bytes, expected);
|
|
}
|
|
}
|