pr 35
This commit is contained in:
parent
c797be9287
commit
8c161e3e13
@ -20,10 +20,36 @@ pub fn operand_size(opcode: OpCode) -> usize {
|
|||||||
OpCode::GetLocal | OpCode::SetLocal => 4,
|
OpCode::GetLocal | OpCode::SetLocal => 4,
|
||||||
OpCode::Call => 8, // addr(u32) + args_count(u32)
|
OpCode::Call => 8, // addr(u32) + args_count(u32)
|
||||||
OpCode::Syscall => 4,
|
OpCode::Syscall => 4,
|
||||||
|
OpCode::Alloc => 8, // type_id(u32) + slots(u32)
|
||||||
|
OpCode::GateLoad | OpCode::GateStore => 4, // offset(u32)
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- HIP Trap Codes ---
|
||||||
|
|
||||||
|
/// Attempted to access a gate that does not exist or has been recycled incorrectly.
|
||||||
|
pub const TRAP_INVALID_GATE: u32 = 0x01;
|
||||||
|
/// Attempted to access a gate that has been explicitly released (RC=0).
|
||||||
|
pub const TRAP_DEAD_GATE: u32 = 0x02;
|
||||||
|
/// Attempted to access a field or index beyond the allocated slots for a gate.
|
||||||
|
pub const TRAP_OOB: u32 = 0x03;
|
||||||
|
/// Attempted a typed operation on a gate whose storage type does not match.
|
||||||
|
pub const TRAP_TYPE: u32 = 0x04;
|
||||||
|
|
||||||
|
/// Detailed information about a runtime trap.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct TrapInfo {
|
||||||
|
/// The specific trap code (e.g., TRAP_OOB).
|
||||||
|
pub code: u32,
|
||||||
|
/// The numeric value of the opcode that triggered the trap.
|
||||||
|
pub opcode: u16,
|
||||||
|
/// A human-readable message explaining the trap.
|
||||||
|
pub message: String,
|
||||||
|
/// The absolute Program Counter (PC) address where the trap occurred.
|
||||||
|
pub pc: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if an instruction is a jump (branch) instruction.
|
/// Checks if an instruction is a jump (branch) instruction.
|
||||||
pub fn is_jump(opcode: OpCode) -> bool {
|
pub fn is_jump(opcode: OpCode) -> bool {
|
||||||
match opcode {
|
match opcode {
|
||||||
@ -36,3 +62,47 @@ pub fn is_jump(opcode: OpCode) -> bool {
|
|||||||
pub fn has_immediate(opcode: OpCode) -> bool {
|
pub fn has_immediate(opcode: OpCode) -> bool {
|
||||||
operand_size(opcode) > 0
|
operand_size(opcode) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trap_code_stability() {
|
||||||
|
// These numeric values are normative and must not change.
|
||||||
|
assert_eq!(TRAP_INVALID_GATE, 0x01);
|
||||||
|
assert_eq!(TRAP_DEAD_GATE, 0x02);
|
||||||
|
assert_eq!(TRAP_OOB, 0x03);
|
||||||
|
assert_eq!(TRAP_TYPE, 0x04);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_abi_documentation_snapshot() {
|
||||||
|
// Snapshot of the ABI rules for traps and operands.
|
||||||
|
let abi_info = r#"
|
||||||
|
HIP Traps:
|
||||||
|
- INVALID_GATE (0x01): Non-existent gate handle.
|
||||||
|
- DEAD_GATE (0x02): Gate handle with RC=0.
|
||||||
|
- OOB (0x03): Access beyond allocated slots.
|
||||||
|
- TYPE (0x04): Type mismatch during heap access.
|
||||||
|
|
||||||
|
Operand Sizes:
|
||||||
|
- Alloc: 8 bytes (u32 type_id, u32 slots)
|
||||||
|
- GateLoad: 4 bytes (u32 offset)
|
||||||
|
- GateStore: 4 bytes (u32 offset)
|
||||||
|
- PopN: 4 bytes (u32 count)
|
||||||
|
"#;
|
||||||
|
// This test serves as a "doc-lock".
|
||||||
|
// If you change the ABI, you must update this string.
|
||||||
|
let current_info = format!(
|
||||||
|
"\nHIP Traps:\n- INVALID_GATE (0x{:02X}): Non-existent gate handle.\n- DEAD_GATE (0x{:02X}): Gate handle with RC=0.\n- OOB (0x{:02X}): Access beyond allocated slots.\n- TYPE (0x{:02X}): Type mismatch during heap access.\n\nOperand Sizes:\n- Alloc: {} bytes (u32 type_id, u32 slots)\n- GateLoad: {} bytes (u32 offset)\n- GateStore: {} bytes (u32 offset)\n- PopN: {} bytes (u32 count)\n",
|
||||||
|
TRAP_INVALID_GATE, TRAP_DEAD_GATE, TRAP_OOB, TRAP_TYPE,
|
||||||
|
operand_size(OpCode::Alloc),
|
||||||
|
operand_size(OpCode::GateLoad),
|
||||||
|
operand_size(OpCode::GateStore),
|
||||||
|
operand_size(OpCode::PopN)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(current_info.trim(), abi_info.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -26,12 +26,16 @@ pub enum ConstantPoolEntry {
|
|||||||
///
|
///
|
||||||
/// The file format follows this structure (Little-Endian):
|
/// The file format follows this structure (Little-Endian):
|
||||||
/// 1. Magic Header: "PPBC" (4 bytes)
|
/// 1. Magic Header: "PPBC" (4 bytes)
|
||||||
/// 2. CP Count: u32
|
/// 2. Version: u16 (Currently 0)
|
||||||
/// 3. CP Entries: [Tag (u8), Data...]
|
/// 3. Flags: u16 (Reserved)
|
||||||
/// 4. ROM Size: u32
|
/// 4. CP Count: u32
|
||||||
/// 5. ROM Data: [u16 OpCode, Operands...][]
|
/// 5. CP Entries: [Tag (u8), Data...]
|
||||||
|
/// 6. ROM Size: u32
|
||||||
|
/// 7. ROM Data: [u16 OpCode, Operands...][]
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct PbcFile {
|
pub struct PbcFile {
|
||||||
|
/// The file format version.
|
||||||
|
pub version: u16,
|
||||||
/// The list of constants used by the program.
|
/// The list of constants used by the program.
|
||||||
pub cp: Vec<ConstantPoolEntry>,
|
pub cp: Vec<ConstantPoolEntry>,
|
||||||
/// The raw instruction bytes (ROM).
|
/// The raw instruction bytes (ROM).
|
||||||
@ -43,12 +47,15 @@ pub struct PbcFile {
|
|||||||
/// This function validates the "PPBC" signature and reconstructs the
|
/// This function validates the "PPBC" signature and reconstructs the
|
||||||
/// Constant Pool and ROM data from the binary format.
|
/// Constant Pool and ROM data from the binary format.
|
||||||
pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
||||||
if bytes.len() < 4 || &bytes[0..4] != b"PPBC" {
|
if bytes.len() < 8 || &bytes[0..4] != b"PPBC" {
|
||||||
return Err("Invalid PBC signature".into());
|
return Err("Invalid PBC signature".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cursor = Cursor::new(&bytes[4..]);
|
let mut cursor = Cursor::new(&bytes[4..]);
|
||||||
|
|
||||||
|
let version = read_u16_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||||
|
let _flags = read_u16_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let cp_count = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as usize;
|
let cp_count = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as usize;
|
||||||
let mut cp = Vec::with_capacity(cp_count);
|
let mut cp = Vec::with_capacity(cp_count);
|
||||||
|
|
||||||
@ -90,7 +97,7 @@ pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
|||||||
let mut rom = vec![0u8; rom_size];
|
let mut rom = vec![0u8; rom_size];
|
||||||
cursor.read_exact(&mut rom).map_err(|e| e.to_string())?;
|
cursor.read_exact(&mut rom).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
Ok(PbcFile { cp, rom })
|
Ok(PbcFile { version, cp, rom })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes a `PbcFile` structure into a binary buffer.
|
/// Serializes a `PbcFile` structure into a binary buffer.
|
||||||
@ -100,6 +107,9 @@ pub fn write_pbc(pbc: &PbcFile) -> Result<Vec<u8>, String> {
|
|||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
out.write_all(b"PPBC").map_err(|e| e.to_string())?;
|
out.write_all(b"PPBC").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
write_u16_le(&mut out, pbc.version).map_err(|e| e.to_string())?;
|
||||||
|
write_u16_le(&mut out, 0).map_err(|e| e.to_string())?; // Flags reserved
|
||||||
|
|
||||||
write_u32_le(&mut out, pbc.cp.len() as u32).map_err(|e| e.to_string())?;
|
write_u32_le(&mut out, pbc.cp.len() as u32).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
for entry in &pbc.cp {
|
for entry in &pbc.cp {
|
||||||
@ -157,6 +167,7 @@ mod tests {
|
|||||||
|
|
||||||
// 2. Create a PBC file
|
// 2. Create a PBC file
|
||||||
let pbc_file = PbcFile {
|
let pbc_file = PbcFile {
|
||||||
|
version: 0,
|
||||||
cp: vec![ConstantPoolEntry::Int32(100)], // Random CP entry
|
cp: vec![ConstantPoolEntry::Int32(100)], // Random CP entry
|
||||||
rom,
|
rom,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -227,6 +227,7 @@ impl<'a> BytecodeEmitter<'a> {
|
|||||||
// --- PHASE 4: Serialization ---
|
// --- PHASE 4: Serialization ---
|
||||||
// Packages the constant pool and bytecode into the final PBC format.
|
// Packages the constant pool and bytecode into the final PBC format.
|
||||||
let pbc = PbcFile {
|
let pbc = PbcFile {
|
||||||
|
version: 0,
|
||||||
cp: self.constant_pool.clone(),
|
cp: self.constant_pool.clone(),
|
||||||
rom: bytecode,
|
rom: bytecode,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,10 +6,8 @@ use std::sync::Arc;
|
|||||||
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
||||||
///
|
///
|
||||||
/// ### Usage Example:
|
/// ### Usage Example:
|
||||||
/// ```rust
|
|
||||||
/// // Draw a semi-transparent blue rectangle
|
/// // Draw a semi-transparent blue rectangle
|
||||||
/// gfx.fill_rect_blend(10, 10, 50, 50, Color::BLUE, BlendMode::Half);
|
/// gfx.fill_rect_blend(10, 10, 50, 50, Color::BLUE, BlendMode::Half);
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
/// No blending: a source overwrites the destination.
|
/// No blending: a source overwrites the destination.
|
||||||
|
|||||||
@ -21,7 +21,7 @@ pub enum Value {
|
|||||||
/// UTF-8 string. Strings are immutable and usually come from the Constant Pool.
|
/// UTF-8 string. Strings are immutable and usually come from the Constant Pool.
|
||||||
String(String),
|
String(String),
|
||||||
/// A pointer to an object on the heap.
|
/// A pointer to an object on the heap.
|
||||||
Ref(usize),
|
Gate(usize),
|
||||||
/// Represents the absence of a value (equivalent to `null` or `undefined`).
|
/// Represents the absence of a value (equivalent to `null` or `undefined`).
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ impl PartialEq for Value {
|
|||||||
(Value::Float(a), Value::Int64(b)) => *a == *b as f64,
|
(Value::Float(a), Value::Int64(b)) => *a == *b as f64,
|
||||||
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
(Value::Boolean(a), Value::Boolean(b)) => a == b,
|
||||||
(Value::String(a), Value::String(b)) => a == b,
|
(Value::String(a), Value::String(b)) => a == b,
|
||||||
(Value::Ref(a), Value::Ref(b)) => a == b,
|
(Value::Gate(a), Value::Gate(b)) => a == b,
|
||||||
(Value::Null, Value::Null) => true,
|
(Value::Null, Value::Null) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ impl Value {
|
|||||||
Value::Float(f) => f.to_string(),
|
Value::Float(f) => f.to_string(),
|
||||||
Value::Boolean(b) => b.to_string(),
|
Value::Boolean(b) => b.to_string(),
|
||||||
Value::String(s) => s.clone(),
|
Value::String(s) => s.clone(),
|
||||||
Value::Ref(r) => format!("[Ref {}]", r),
|
Value::Gate(r) => format!("[Gate {}]", r),
|
||||||
Value::Null => "null".to_string(),
|
Value::Null => "null".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,12 @@ use crate::virtual_machine::value::Value;
|
|||||||
use crate::virtual_machine::{NativeInterface, Program};
|
use crate::virtual_machine::{NativeInterface, Program};
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
||||||
|
use prometeu_bytecode::abi::TrapInfo;
|
||||||
|
|
||||||
/// Reason why the Virtual Machine stopped execution during a specific run.
|
/// Reason why the Virtual Machine stopped execution during a specific run.
|
||||||
/// This allows the system to decide if it should continue execution in the next tick
|
/// This allows the system to decide if it should continue execution in the next tick
|
||||||
/// or if the frame is finalized.
|
/// or if the frame is finalized.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum LogicalFrameEndingReason {
|
pub enum LogicalFrameEndingReason {
|
||||||
/// Execution reached a `FRAME_SYNC` instruction, marking the end of the logical frame.
|
/// Execution reached a `FRAME_SYNC` instruction, marking the end of the logical frame.
|
||||||
FrameSync,
|
FrameSync,
|
||||||
@ -21,10 +22,14 @@ pub enum LogicalFrameEndingReason {
|
|||||||
EndOfRom,
|
EndOfRom,
|
||||||
/// Execution hit a registered breakpoint.
|
/// Execution hit a registered breakpoint.
|
||||||
Breakpoint,
|
Breakpoint,
|
||||||
|
/// A runtime trap occurred (e.g., OOB, invalid gate).
|
||||||
|
Trap(TrapInfo),
|
||||||
|
/// A fatal error occurred that cannot be recovered (e.g., stack underflow).
|
||||||
|
Panic(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A report detailing the results of an execution slice (run_budget).
|
/// A report detailing the results of an execution slice (run_budget).
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct BudgetReport {
|
pub struct BudgetReport {
|
||||||
/// Total virtual cycles consumed during this run.
|
/// Total virtual cycles consumed during this run.
|
||||||
pub cycles_used: u64,
|
pub cycles_used: u64,
|
||||||
@ -198,13 +203,17 @@ impl VirtualMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute a single step (Fetch-Decode-Execute)
|
// Execute a single step (Fetch-Decode-Execute)
|
||||||
self.step(native, hw)?;
|
if let Err(reason) = self.step(native, hw) {
|
||||||
|
ending_reason = Some(reason);
|
||||||
|
break;
|
||||||
|
}
|
||||||
steps_executed += 1;
|
steps_executed += 1;
|
||||||
|
|
||||||
// Integrity check: ensure real progress is being made to avoid infinite loops
|
// Integrity check: ensure real progress is being made to avoid infinite loops
|
||||||
// caused by zero-cycle instructions or stuck PC.
|
// caused by zero-cycle instructions or stuck PC.
|
||||||
if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
|
if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
|
||||||
return Err(format!("VM stuck at PC 0x{:08X}", self.pc));
|
ending_reason = Some(LogicalFrameEndingReason::Panic(format!("VM stuck at PC 0x{:08X}", self.pc)));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,14 +253,16 @@ impl VirtualMachine {
|
|||||||
/// 1. Fetch: Read the opcode from memory.
|
/// 1. Fetch: Read the opcode from memory.
|
||||||
/// 2. Decode: Identify what operation to perform.
|
/// 2. Decode: Identify what operation to perform.
|
||||||
/// 3. Execute: Perform the operation, updating stacks, memory, or calling peripherals.
|
/// 3. Execute: Perform the operation, updating stacks, memory, or calling peripherals.
|
||||||
pub fn step(&mut self, native: &mut dyn NativeInterface, hw: &mut dyn HardwareBridge) -> Result<(), String> {
|
pub fn step(&mut self, native: &mut dyn NativeInterface, hw: &mut dyn HardwareBridge) -> Result<(), LogicalFrameEndingReason> {
|
||||||
if self.halted || self.pc >= self.program.rom.len() {
|
if self.halted || self.pc >= self.program.rom.len() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let start_pc = self.pc;
|
||||||
|
|
||||||
// Fetch & Decode
|
// Fetch & Decode
|
||||||
let opcode_val = self.read_u16()?;
|
let opcode_val = self.read_u16().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let opcode = OpCode::try_from(opcode_val)?;
|
let opcode = OpCode::try_from(opcode_val).map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
match opcode {
|
match opcode {
|
||||||
@ -260,19 +271,19 @@ impl VirtualMachine {
|
|||||||
self.halted = true;
|
self.halted = true;
|
||||||
}
|
}
|
||||||
OpCode::Jmp => {
|
OpCode::Jmp => {
|
||||||
let addr = self.read_u32()? as usize;
|
let addr = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
self.pc = addr;
|
self.pc = addr;
|
||||||
}
|
}
|
||||||
OpCode::JmpIfFalse => {
|
OpCode::JmpIfFalse => {
|
||||||
let addr = self.read_u32()? as usize;
|
let addr = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
if let Value::Boolean(false) = val {
|
if let Value::Boolean(false) = val {
|
||||||
self.pc = addr;
|
self.pc = addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::JmpIfTrue => {
|
OpCode::JmpIfTrue => {
|
||||||
let addr = self.read_u32()? as usize;
|
let addr = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
if let Value::Boolean(true) = val {
|
if let Value::Boolean(true) = val {
|
||||||
self.pc = addr;
|
self.pc = addr;
|
||||||
}
|
}
|
||||||
@ -282,42 +293,42 @@ impl VirtualMachine {
|
|||||||
// but we need to advance PC if executed via step() directly.
|
// 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().map_err(|e| LogicalFrameEndingReason::Panic(e))? 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_else(|| LogicalFrameEndingReason::Panic("Invalid constant index".into()))?;
|
||||||
self.push(val);
|
self.push(val);
|
||||||
}
|
}
|
||||||
OpCode::PushI64 => {
|
OpCode::PushI64 => {
|
||||||
let val = self.read_i64()?;
|
let val = self.read_i64().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
self.push(Value::Int64(val));
|
self.push(Value::Int64(val));
|
||||||
}
|
}
|
||||||
OpCode::PushI32 => {
|
OpCode::PushI32 => {
|
||||||
let val = self.read_i32()?;
|
let val = self.read_i32().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
self.push(Value::Int32(val));
|
self.push(Value::Int32(val));
|
||||||
}
|
}
|
||||||
OpCode::PushF64 => {
|
OpCode::PushF64 => {
|
||||||
let val = self.read_f64()?;
|
let val = self.read_f64().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
self.push(Value::Float(val));
|
self.push(Value::Float(val));
|
||||||
}
|
}
|
||||||
OpCode::PushBool => {
|
OpCode::PushBool => {
|
||||||
let val = self.read_u8()?;
|
let val = self.read_u8().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
self.push(Value::Boolean(val != 0));
|
self.push(Value::Boolean(val != 0));
|
||||||
}
|
}
|
||||||
OpCode::Pop => {
|
OpCode::Pop => {
|
||||||
self.pop()?;
|
self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
}
|
}
|
||||||
OpCode::PopN => {
|
OpCode::PopN => {
|
||||||
let n = self.read_u16()?;
|
let n = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
self.pop()?;
|
self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::Dup => {
|
OpCode::Dup => {
|
||||||
let val = self.peek()?.clone();
|
let val = self.peek().map_err(|e| LogicalFrameEndingReason::Panic(e))?.clone();
|
||||||
self.push(val);
|
self.push(val);
|
||||||
}
|
}
|
||||||
OpCode::Swap => {
|
OpCode::Swap => {
|
||||||
let a = self.pop()?;
|
let a = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let b = self.pop()?;
|
let b = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
self.push(a);
|
self.push(a);
|
||||||
self.push(b);
|
self.push(b);
|
||||||
}
|
}
|
||||||
@ -335,7 +346,7 @@ impl VirtualMachine {
|
|||||||
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
|
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(*a as f64 + b)),
|
||||||
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a + *b as f64)),
|
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a + *b as f64)),
|
||||||
_ => Err("Invalid types for ADD".into()),
|
_ => Err("Invalid types for ADD".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Sub => self.binary_op(|a, b| match (a, b) {
|
OpCode::Sub => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_sub(b))),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_sub(b))),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_sub(b))),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_sub(b))),
|
||||||
@ -347,7 +358,7 @@ impl VirtualMachine {
|
|||||||
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
|
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 - b)),
|
||||||
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a - b as f64)),
|
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a - b as f64)),
|
||||||
_ => Err("Invalid types for SUB".into()),
|
_ => Err("Invalid types for SUB".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Mul => self.binary_op(|a, b| match (a, b) {
|
OpCode::Mul => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_mul(b))),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_mul(b))),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_mul(b))),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_mul(b))),
|
||||||
@ -359,7 +370,7 @@ impl VirtualMachine {
|
|||||||
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
|
(Value::Int64(a), Value::Float(b)) => Ok(Value::Float(a as f64 * b)),
|
||||||
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a * b as f64)),
|
(Value::Float(a), Value::Int64(b)) => Ok(Value::Float(a * b as f64)),
|
||||||
_ => Err("Invalid types for MUL".into()),
|
_ => Err("Invalid types for MUL".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Div => self.binary_op(|a, b| match (a, b) {
|
OpCode::Div => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => {
|
(Value::Int32(a), Value::Int32(b)) => {
|
||||||
if b == 0 { return Err("Division by zero".into()); }
|
if b == 0 { return Err("Division by zero".into()); }
|
||||||
@ -398,43 +409,43 @@ impl VirtualMachine {
|
|||||||
Ok(Value::Float(a / b as f64))
|
Ok(Value::Float(a / b as f64))
|
||||||
}
|
}
|
||||||
_ => Err("Invalid types for DIV".into()),
|
_ => Err("Invalid types for DIV".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b)))?,
|
OpCode::Eq => self.binary_op(|a, b| Ok(Value::Boolean(a == b))).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b)))?,
|
OpCode::Neq => self.binary_op(|a, b| Ok(Value::Boolean(a != b))).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Lt => self.binary_op(|a, b| {
|
OpCode::Lt => self.binary_op(|a, b| {
|
||||||
a.partial_cmp(&b)
|
a.partial_cmp(&b)
|
||||||
.map(|o| Value::Boolean(o == std::cmp::Ordering::Less))
|
.map(|o| Value::Boolean(o == std::cmp::Ordering::Less))
|
||||||
.ok_or_else(|| "Invalid types for LT".into())
|
.ok_or_else(|| "Invalid types for LT".into())
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Gt => self.binary_op(|a, b| {
|
OpCode::Gt => self.binary_op(|a, b| {
|
||||||
a.partial_cmp(&b)
|
a.partial_cmp(&b)
|
||||||
.map(|o| Value::Boolean(o == std::cmp::Ordering::Greater))
|
.map(|o| Value::Boolean(o == std::cmp::Ordering::Greater))
|
||||||
.ok_or_else(|| "Invalid types for GT".into())
|
.ok_or_else(|| "Invalid types for GT".into())
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Lte => self.binary_op(|a, b| {
|
OpCode::Lte => self.binary_op(|a, b| {
|
||||||
a.partial_cmp(&b)
|
a.partial_cmp(&b)
|
||||||
.map(|o| Value::Boolean(o != std::cmp::Ordering::Greater))
|
.map(|o| Value::Boolean(o != std::cmp::Ordering::Greater))
|
||||||
.ok_or_else(|| "Invalid types for LTE".into())
|
.ok_or_else(|| "Invalid types for LTE".into())
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Gte => self.binary_op(|a, b| {
|
OpCode::Gte => self.binary_op(|a, b| {
|
||||||
a.partial_cmp(&b)
|
a.partial_cmp(&b)
|
||||||
.map(|o| Value::Boolean(o != std::cmp::Ordering::Less))
|
.map(|o| Value::Boolean(o != std::cmp::Ordering::Less))
|
||||||
.ok_or_else(|| "Invalid types for GTE".into())
|
.ok_or_else(|| "Invalid types for GTE".into())
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::And => self.binary_op(|a, b| match (a, b) {
|
OpCode::And => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)),
|
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a && b)),
|
||||||
_ => Err("Invalid types for AND".into()),
|
_ => Err("Invalid types for AND".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Or => self.binary_op(|a, b| match (a, b) {
|
OpCode::Or => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a || b)),
|
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Boolean(a || b)),
|
||||||
_ => Err("Invalid types for OR".into()),
|
_ => Err("Invalid types for OR".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Not => {
|
OpCode::Not => {
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
if let Value::Boolean(b) = val {
|
if let Value::Boolean(b) = val {
|
||||||
self.push(Value::Boolean(!b));
|
self.push(Value::Boolean(!b));
|
||||||
} else {
|
} else {
|
||||||
return Err("Invalid type for NOT".into());
|
return Err(LogicalFrameEndingReason::Panic("Invalid type for NOT".into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::BitAnd => self.binary_op(|a, b| match (a, b) {
|
OpCode::BitAnd => self.binary_op(|a, b| match (a, b) {
|
||||||
@ -443,78 +454,76 @@ impl VirtualMachine {
|
|||||||
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) & b)),
|
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) & b)),
|
||||||
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a & (b as i64))),
|
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a & (b as i64))),
|
||||||
_ => Err("Invalid types for BitAnd".into()),
|
_ => Err("Invalid types for BitAnd".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::BitOr => self.binary_op(|a, b| match (a, b) {
|
OpCode::BitOr => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a | b)),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a | b)),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a | b)),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a | b)),
|
||||||
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) | b)),
|
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) | b)),
|
||||||
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a | (b as i64))),
|
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a | (b as i64))),
|
||||||
_ => Err("Invalid types for BitOr".into()),
|
_ => Err("Invalid types for BitOr".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::BitXor => self.binary_op(|a, b| match (a, b) {
|
OpCode::BitXor => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a ^ b)),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a ^ b)),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a ^ b)),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a ^ b)),
|
||||||
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) ^ b)),
|
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64) ^ b)),
|
||||||
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a ^ (b as i64))),
|
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a ^ (b as i64))),
|
||||||
_ => Err("Invalid types for BitXor".into()),
|
_ => Err("Invalid types for BitXor".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Shl => self.binary_op(|a, b| match (a, b) {
|
OpCode::Shl => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shl(b as u32))),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shl(b as u32))),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
|
||||||
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shl(b as u32))),
|
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shl(b as u32))),
|
||||||
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
|
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shl(b as u32))),
|
||||||
_ => Err("Invalid types for Shl".into()),
|
_ => Err("Invalid types for Shl".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Shr => self.binary_op(|a, b| match (a, b) {
|
OpCode::Shr => self.binary_op(|a, b| match (a, b) {
|
||||||
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shr(b as u32))),
|
(Value::Int32(a), Value::Int32(b)) => Ok(Value::Int32(a.wrapping_shr(b as u32))),
|
||||||
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
|
(Value::Int64(a), Value::Int64(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
|
||||||
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shr(b as u32))),
|
(Value::Int32(a), Value::Int64(b)) => Ok(Value::Int64((a as i64).wrapping_shr(b as u32))),
|
||||||
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
|
(Value::Int64(a), Value::Int32(b)) => Ok(Value::Int64(a.wrapping_shr(b as u32))),
|
||||||
_ => Err("Invalid types for Shr".into()),
|
_ => Err("Invalid types for Shr".into()),
|
||||||
})?,
|
}).map_err(|e| LogicalFrameEndingReason::Panic(e))?,
|
||||||
OpCode::Neg => {
|
OpCode::Neg => {
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
match val {
|
match val {
|
||||||
Value::Int32(a) => self.push(Value::Int32(a.wrapping_neg())),
|
Value::Int32(a) => self.push(Value::Int32(a.wrapping_neg())),
|
||||||
Value::Int64(a) => self.push(Value::Int64(a.wrapping_neg())),
|
Value::Int64(a) => self.push(Value::Int64(a.wrapping_neg())),
|
||||||
Value::Float(a) => self.push(Value::Float(-a)),
|
Value::Float(a) => self.push(Value::Float(-a)),
|
||||||
_ => return Err("Invalid type for Neg".into()),
|
_ => return Err(LogicalFrameEndingReason::Panic("Invalid type for Neg".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::GetGlobal => {
|
OpCode::GetGlobal => {
|
||||||
let idx = self.read_u32()? as usize;
|
let idx = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let val = self.globals.get(idx).cloned().ok_or("Invalid global index")?;
|
let val = self.globals.get(idx).cloned().ok_or_else(|| LogicalFrameEndingReason::Panic("Invalid global index".into()))?;
|
||||||
self.push(val);
|
self.push(val);
|
||||||
}
|
}
|
||||||
OpCode::SetGlobal => {
|
OpCode::SetGlobal => {
|
||||||
let idx = self.read_u32()? as usize;
|
let idx = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
if idx >= self.globals.len() {
|
if idx >= self.globals.len() {
|
||||||
self.globals.resize(idx + 1, Value::Null);
|
self.globals.resize(idx + 1, Value::Null);
|
||||||
}
|
}
|
||||||
self.globals[idx] = val;
|
self.globals[idx] = val;
|
||||||
}
|
}
|
||||||
OpCode::GetLocal => {
|
OpCode::GetLocal => {
|
||||||
let idx = self.read_u32()? as usize;
|
let idx = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let frame = self.call_stack.last().ok_or("No active call frame")?;
|
let frame = self.call_stack.last().ok_or_else(|| LogicalFrameEndingReason::Panic("No active call frame".into()))?;
|
||||||
let val = self.operand_stack.get(frame.stack_base + idx).cloned().ok_or("Invalid local index")?;
|
let val = self.operand_stack.get(frame.stack_base + idx).cloned().ok_or_else(|| LogicalFrameEndingReason::Panic("Invalid local index".into()))?;
|
||||||
self.push(val);
|
self.push(val);
|
||||||
}
|
}
|
||||||
OpCode::SetLocal => {
|
OpCode::SetLocal => {
|
||||||
let idx = self.read_u32()? as usize;
|
let idx = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let val = self.pop()?;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let frame = self.call_stack.last().ok_or("No active call frame")?;
|
let frame = self.call_stack.last().ok_or_else(|| LogicalFrameEndingReason::Panic("No active call frame".into()))?;
|
||||||
let stack_idx = frame.stack_base + idx;
|
let stack_idx = frame.stack_base + idx;
|
||||||
if stack_idx >= self.operand_stack.len() {
|
if stack_idx >= self.operand_stack.len() {
|
||||||
return Err("Local index out of bounds".into());
|
return Err(LogicalFrameEndingReason::Panic("Local index out of bounds".into()));
|
||||||
}
|
}
|
||||||
self.operand_stack[stack_idx] = val;
|
self.operand_stack[stack_idx] = val;
|
||||||
}
|
}
|
||||||
OpCode::Call => {
|
OpCode::Call => {
|
||||||
// addr: destination instruction address
|
let addr = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
// args_count: how many values from the operand stack become locals in the new frame
|
let args_count = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let addr = self.read_u32()? as usize;
|
|
||||||
let args_count = self.read_u32()? as usize;
|
|
||||||
let stack_base = self.operand_stack.len() - args_count;
|
let stack_base = self.operand_stack.len() - args_count;
|
||||||
self.call_stack.push(CallFrame {
|
self.call_stack.push(CallFrame {
|
||||||
return_pc: self.pc as u32,
|
return_pc: self.pc as u32,
|
||||||
@ -523,81 +532,89 @@ impl VirtualMachine {
|
|||||||
self.pc = addr;
|
self.pc = addr;
|
||||||
}
|
}
|
||||||
OpCode::Ret => {
|
OpCode::Ret => {
|
||||||
let frame = self.call_stack.pop().ok_or("Call stack underflow")?;
|
let frame = self.call_stack.pop().ok_or_else(|| LogicalFrameEndingReason::Panic("Call stack underflow".into()))?;
|
||||||
// ABI Rule: Every function MUST leave exactly one value on the stack before RET.
|
let return_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
// This value is popped before cleaning the stack and re-pushed after.
|
|
||||||
let return_val = self.pop()?;
|
|
||||||
// Clean up the operand stack, removing the frame's locals
|
|
||||||
self.operand_stack.truncate(frame.stack_base);
|
self.operand_stack.truncate(frame.stack_base);
|
||||||
// Return the result of the function
|
|
||||||
self.push(return_val);
|
self.push(return_val);
|
||||||
self.pc = frame.return_pc as usize;
|
self.pc = frame.return_pc as usize;
|
||||||
}
|
}
|
||||||
OpCode::PushScope => {
|
OpCode::PushScope => {
|
||||||
// Used for blocks within a function that have their own locals
|
|
||||||
self.scope_stack.push(ScopeFrame {
|
self.scope_stack.push(ScopeFrame {
|
||||||
scope_stack_base: self.operand_stack.len(),
|
scope_stack_base: self.operand_stack.len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
OpCode::PopScope => {
|
OpCode::PopScope => {
|
||||||
let frame = self.scope_stack.pop().ok_or("Scope stack underflow")?;
|
let frame = self.scope_stack.pop().ok_or_else(|| LogicalFrameEndingReason::Panic("Scope stack underflow".into()))?;
|
||||||
self.operand_stack.truncate(frame.scope_stack_base);
|
self.operand_stack.truncate(frame.scope_stack_base);
|
||||||
}
|
}
|
||||||
OpCode::Alloc => {
|
OpCode::Alloc => {
|
||||||
// Allocates 'slots' values on the heap and pushes a reference to the stack
|
let _type_id = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let _type_id = self.read_u32()?;
|
let slots = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let slots = self.read_u32()? as usize;
|
|
||||||
let ref_idx = self.heap.len();
|
let ref_idx = self.heap.len();
|
||||||
for _ in 0..slots {
|
for _ in 0..slots {
|
||||||
self.heap.push(Value::Null);
|
self.heap.push(Value::Null);
|
||||||
}
|
}
|
||||||
self.push(Value::Ref(ref_idx));
|
self.push(Value::Gate(ref_idx));
|
||||||
}
|
}
|
||||||
OpCode::GateLoad => {
|
OpCode::GateLoad => {
|
||||||
// Reads a value from a heap reference at a specific offset
|
let offset = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let offset = self.read_u32()? as usize;
|
let ref_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let ref_val = self.pop()?;
|
if let Value::Gate(base) = ref_val {
|
||||||
if let Value::Ref(base) = ref_val {
|
let val = self.heap.get(base + offset).cloned().ok_or_else(|| {
|
||||||
let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?;
|
LogicalFrameEndingReason::Trap(TrapInfo {
|
||||||
|
code: prometeu_bytecode::abi::TRAP_OOB,
|
||||||
|
opcode: OpCode::GateLoad as u16,
|
||||||
|
message: format!("Out-of-bounds heap access at offset {}", offset),
|
||||||
|
pc: start_pc as u32,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
self.push(val);
|
self.push(val);
|
||||||
} else {
|
} else {
|
||||||
return Err("Expected reference for GATE_LOAD".into());
|
return Err(LogicalFrameEndingReason::Trap(TrapInfo {
|
||||||
|
code: prometeu_bytecode::abi::TRAP_TYPE,
|
||||||
|
opcode: OpCode::GateLoad as u16,
|
||||||
|
message: "Expected gate handle for GATE_LOAD".to_string(),
|
||||||
|
pc: start_pc as u32,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::GateStore => {
|
OpCode::GateStore => {
|
||||||
// Writes a value to a heap reference at a specific offset
|
let offset = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))? as usize;
|
||||||
let offset = self.read_u32()? as usize;
|
let val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let val = self.pop()?;
|
let ref_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
let ref_val = self.pop()?;
|
if let Value::Gate(base) = ref_val {
|
||||||
if let Value::Ref(base) = ref_val {
|
|
||||||
if base + offset >= self.heap.len() {
|
if base + offset >= self.heap.len() {
|
||||||
return Err("Invalid heap access".into());
|
return Err(LogicalFrameEndingReason::Trap(TrapInfo {
|
||||||
|
code: prometeu_bytecode::abi::TRAP_OOB,
|
||||||
|
opcode: OpCode::GateStore as u16,
|
||||||
|
message: format!("Out-of-bounds heap access at offset {}", offset),
|
||||||
|
pc: start_pc as u32,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
self.heap[base + offset] = val;
|
self.heap[base + offset] = val;
|
||||||
} else {
|
} else {
|
||||||
return Err("Expected reference for GATE_STORE".into());
|
return Err(LogicalFrameEndingReason::Trap(TrapInfo {
|
||||||
|
code: prometeu_bytecode::abi::TRAP_TYPE,
|
||||||
|
opcode: OpCode::GateStore as u16,
|
||||||
|
message: "Expected gate handle for GATE_STORE".to_string(),
|
||||||
|
pc: start_pc as u32,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OpCode::GateBeginPeek | OpCode::GateEndPeek |
|
OpCode::GateBeginPeek | OpCode::GateEndPeek |
|
||||||
OpCode::GateBeginBorrow | OpCode::GateEndBorrow |
|
OpCode::GateBeginBorrow | OpCode::GateEndBorrow |
|
||||||
OpCode::GateBeginMutate | OpCode::GateEndMutate |
|
OpCode::GateBeginMutate | OpCode::GateEndMutate |
|
||||||
OpCode::GateRetain => {
|
OpCode::GateRetain => {
|
||||||
// These are no-ops in v0, but they preserve the gate on the stack.
|
|
||||||
}
|
}
|
||||||
OpCode::GateRelease => {
|
OpCode::GateRelease => {
|
||||||
self.pop()?;
|
self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
}
|
}
|
||||||
OpCode::Syscall => {
|
OpCode::Syscall => {
|
||||||
// Calls a native function implemented by the Firmware/OS.
|
let id = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||||
// ABI Rule: Arguments are pushed in call order (LIFO).
|
let native_cycles = native.syscall(id, self, hw).map_err(|e| LogicalFrameEndingReason::Panic(format!("syscall 0x{:08X} failed: {}", id, e)))?;
|
||||||
// The native implementation is responsible for popping all arguments
|
|
||||||
// and pushing a return value if applicable.
|
|
||||||
let id = self.read_u32()?;
|
|
||||||
let native_cycles = native.syscall(id, self, hw).map_err(|e| format!("syscall 0x{:08X} failed: {}", id, e))?;
|
|
||||||
self.cycles += native_cycles;
|
self.cycles += native_cycles;
|
||||||
}
|
}
|
||||||
OpCode::FrameSync => {
|
OpCode::FrameSync => {
|
||||||
// Already handled in the run_budget loop for performance
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -887,7 +904,10 @@ mod tests {
|
|||||||
vm.step(&mut native, &mut hw).unwrap(); // CALL
|
vm.step(&mut native, &mut hw).unwrap(); // CALL
|
||||||
let res = vm.step(&mut native, &mut hw); // RET -> should fail
|
let res = vm.step(&mut native, &mut hw); // RET -> should fail
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
assert!(res.unwrap_err().contains("Stack underflow"));
|
match res.unwrap_err() {
|
||||||
|
LogicalFrameEndingReason::Panic(msg) => assert!(msg.contains("Stack underflow")),
|
||||||
|
_ => panic!("Expected Panic"),
|
||||||
|
}
|
||||||
|
|
||||||
// Agora com valor de retorno
|
// Agora com valor de retorno
|
||||||
let mut rom2 = Vec::new();
|
let mut rom2 = Vec::new();
|
||||||
@ -1194,7 +1214,7 @@ mod tests {
|
|||||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).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(&3i32.to_le_bytes());
|
||||||
rom.extend_from_slice(&(OpCode::PopN as u16).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(&2u32.to_le_bytes());
|
||||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||||
|
|
||||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||||
@ -1203,4 +1223,58 @@ mod tests {
|
|||||||
assert_eq!(vm.pop().unwrap(), Value::Int32(1));
|
assert_eq!(vm.pop().unwrap(), Value::Int32(1));
|
||||||
assert!(vm.pop().is_err()); // Stack should be empty
|
assert!(vm.pop().is_err()); // Stack should be empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hip_traps_oob() {
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut hw = MockHardware;
|
||||||
|
|
||||||
|
// ALLOC int, 1 -> Gate(0)
|
||||||
|
// GATE_LOAD 1 -> TRAP_OOB (size is 1, offset 1 is invalid)
|
||||||
|
let mut rom = Vec::new();
|
||||||
|
rom.extend_from_slice(&(OpCode::Alloc as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&0u32.to_le_bytes()); // type_id
|
||||||
|
rom.extend_from_slice(&1u32.to_le_bytes()); // slots
|
||||||
|
rom.extend_from_slice(&(OpCode::GateLoad as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&1u32.to_le_bytes()); // offset 1
|
||||||
|
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();
|
||||||
|
|
||||||
|
match report.reason {
|
||||||
|
LogicalFrameEndingReason::Trap(trap) => {
|
||||||
|
assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_OOB);
|
||||||
|
assert_eq!(trap.opcode, OpCode::GateLoad as u16);
|
||||||
|
assert!(trap.message.contains("Out-of-bounds"));
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Trap, got {:?}", report.reason),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hip_traps_type() {
|
||||||
|
let mut native = MockNative;
|
||||||
|
let mut hw = MockHardware;
|
||||||
|
|
||||||
|
// PUSH_I32 42
|
||||||
|
// GATE_LOAD 0 -> TRAP_TYPE (Expected gate handle, got Int32)
|
||||||
|
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::GateLoad as u16).to_le_bytes());
|
||||||
|
rom.extend_from_slice(&0u32.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();
|
||||||
|
|
||||||
|
match report.reason {
|
||||||
|
LogicalFrameEndingReason::Trap(trap) => {
|
||||||
|
assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_TYPE);
|
||||||
|
assert_eq!(trap.opcode, OpCode::GateLoad as u16);
|
||||||
|
}
|
||||||
|
_ => panic!("Expected Trap, got {:?}", report.reason),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,3 @@
|
|||||||
## PR-10 — HIP ABI Freeze v0: Trap Conditions + Debug Surface
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Freeze the runtime-visible ABI behavior for HIP operations.
|
|
||||||
|
|
||||||
### Required Content (Normative)
|
|
||||||
|
|
||||||
Add a document (or module-level docs) defining traps:
|
|
||||||
|
|
||||||
* Invalid `GateId` → trap `TRAP_INVALID_GATE`
|
|
||||||
* Dead gate access → trap `TRAP_DEAD_GATE`
|
|
||||||
* Out-of-bounds offset (`offset >= slots`) → trap `TRAP_OOB`
|
|
||||||
* Type mismatch (if enforced) → trap `TRAP_TYPE`
|
|
||||||
|
|
||||||
Define what a trap includes:
|
|
||||||
|
|
||||||
* opcode
|
|
||||||
* message
|
|
||||||
* optional span (if debug info is present)
|
|
||||||
|
|
||||||
### Required Changes
|
|
||||||
|
|
||||||
* Add trap codes/constants in bytecode/VM interface.
|
|
||||||
* Ensure bytecode format reserves space / structure for propagating trap info.
|
|
||||||
|
|
||||||
### Tests (Mandatory)
|
|
||||||
|
|
||||||
* Unit tests verifying trap codes are stable (numeric values frozen).
|
|
||||||
* Doc tests or snapshot for ABI text.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PR-11 — Cross-Layer Conformance Tests: Core→VM→Bytecode (HIP)
|
## PR-11 — Cross-Layer Conformance Tests: Core→VM→Bytecode (HIP)
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user