2026-03-24 13:40:45 +00:00

325 lines
12 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,
/// Remainder of the division of the second top value by the top one (a % b).
/// Stack: [a, b] -> [result]
Mod = 0x24,
// --- 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 by its index in the function table.
/// Operand: func_id (u32)
/// Stack: [arg0, arg1, ...] -> [return_slots...]
Call = 0x50,
/// Returns from the current function.
/// Stack: [return_val] -> [return_val]
Ret = 0x51,
/// Creates a closure capturing values from the operand stack (Model B).
/// Operands: fn_id (u32), capture_count (u32)
/// Stack before: [..., captured_N, ..., captured_1]
/// Pops capture_count values (top-first), preserves order as [captured_1..captured_N]
/// and stores them inside the closure environment. Pushes a HeapRef to the closure.
MakeClosure = 0x52,
/// Calls a closure value with hidden arg0 semantics (Model B).
/// Operand: arg_count (u32) — number of user-supplied args (excludes hidden arg0)
/// Stack before: [..., argN, ..., arg1, closure_ref]
/// Behavior:
/// - Pops `closure_ref` and validates it is a Closure.
/// - Pops `arg_count` user args.
/// - Fetches `fn_id` from the closure and creates a new call frame.
/// - Injects hidden arg0 = closure_ref, followed by user args as arg1..argN.
CallClosure = 0x53,
// --- 7.x Concurrency / Coroutines ---
/// Spawns a new coroutine to run a function with arguments.
/// Operands: fn_id (u32), arg_count (u32)
/// Semantics:
/// - Pops `arg_count` arguments from the current operand stack (top-first),
/// preserving user order as arg1..argN for the callee.
/// - Allocates a new Coroutine object with its own stack and a single entry frame
/// pointing at `fn_id`.
/// - Enqueues the coroutine into the scheduler ready queue.
/// - Does NOT switch execution immediately; current coroutine continues.
Spawn = 0x54,
/// Cooperatively yields the current coroutine. Execution continues
/// until the next VM safepoint (FRAME_SYNC), where the scheduler
/// may switch to another ready coroutine.
Yield = 0x55,
/// Suspends the current coroutine for a number of logical ticks.
/// Operand: duration_ticks (u32)
/// Semantics:
/// - Set the coroutine wake tick to `current_tick + duration_ticks`.
/// - End the current logical frame (as if reaching FRAME_SYNC).
/// - The coroutine will resume execution on or after the wake tick.
Sleep = 0x56,
// --- 6.8 Peripherals and System ---
/// Pre-load host binding call by `SYSC` table index.
/// Operand: sysc_index (u32)
/// This opcode is valid only in PBX artifact form and must be patched by the loader
/// into a final numeric `SYSCALL <id>` before verification or execution.
Hostcall = 0x71,
/// Invokes a final numeric system function (Firmware/OS).
/// Raw `SYSCALL` is valid only after loader patching and is rejected in PBX pre-load artifacts.
/// Operand: syscall_id (u32)
/// Stack: [args...] -> [results...] (depends on syscall)
Syscall = 0x70,
/// Invokes a VM-owned intrinsic by final numeric id.
/// Operand: intrinsic_id (u32)
/// Stack: [args...] -> [results...] (depends on intrinsic metadata)
Intrinsic = 0x72,
/// 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),
0x24 => Ok(OpCode::Mod),
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::MakeClosure),
0x53 => Ok(OpCode::CallClosure),
0x54 => Ok(OpCode::Spawn),
0x55 => Ok(OpCode::Yield),
0x56 => Ok(OpCode::Sleep),
0x70 => Ok(OpCode::Syscall),
0x71 => Ok(OpCode::Hostcall),
0x72 => Ok(OpCode::Intrinsic),
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::Mod => 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::MakeClosure => 8,
OpCode::CallClosure => 6,
OpCode::Spawn => 6,
OpCode::Yield => 1,
OpCode::Sleep => 1,
OpCode::Syscall => 1,
OpCode::Hostcall => 1,
OpCode::Intrinsic => 1,
OpCode::FrameSync => 1,
}
}
}