Nilton Constantino c797be9287
pr 34
2026-01-30 16:07:45 +00:00

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);
}
}