diff --git a/Cargo.toml b/Cargo.toml index e1a441a6..d85cf247 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "crates/compiler/prometeu-bytecode", + "crates/console/prometeu-bytecode", "crates/console/prometeu-drivers", "crates/console/prometeu-firmware", diff --git a/crates/compiler/prometeu-bytecode/README.md b/crates/compiler/prometeu-bytecode/README.md deleted file mode 100644 index 225757d4..00000000 --- a/crates/compiler/prometeu-bytecode/README.md +++ /dev/null @@ -1,152 +0,0 @@ -# prometeu-bytecode - -Official contract (ABI) of the PROMETEU ecosystem. This crate defines the Instruction Set Architecture (ISA), the `.pbc` file format (Prometeu ByteCode), and basic tools for assembly (Assembler) and disassembly (Disassembler). - -## Design - -The PVM (Prometeu Virtual Machine) is a **stack-based** machine. Most instructions operate on values at the top of the operand stack. The default multi-byte data format in ROM is **Little-Endian**. - -### Stack Notation Convention -In the tables below, we use the following notation to represent the state of the stack: -`[a, b] -> [c]` -Means the instruction removes `a` and `b` from the stack (where `b` was at the top) and pushes `c` to the top. - ---- - -## Instruction Set Architecture (ISA) - -### 6.1 Execution Control - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `Nop` | `0x00` | - | `[] -> []` | No operation. | -| `Halt` | `0x01` | - | `[] -> []` | Permanently halts VM execution. | -| `Jmp` | `0x02` | `addr: u32` | `[] -> []` | Unconditional jump to absolute address `addr`. | -| `JmpIfFalse`| `0x03` | `addr: u32` | `[bool] -> []` | Jumps to `addr` if the popped value is `false`. | -| `JmpIfTrue` | `0x04` | `addr: u32` | `[bool] -> []` | Jumps to `addr` if the popped value is `true`. | -| `Trap` | `0x05` | - | `[] -> []` | Debugger interruption (breakpoint). | - -### 6.2 Stack Manipulation - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `PushConst` | `0x10` | `idx: u32` | `[] -> [val]` | Loads the constant at index `idx` from the Constant Pool. | -| `Pop` | `0x11` | - | `[val] -> []` | Removes and discards the top value of the stack. | -| `Dup` | `0x12` | - | `[val] -> [val, val]` | Duplicates the value at the top of the stack. | -| `Swap` | `0x13` | - | `[a, b] -> [b, a]` | Swaps the positions of the two values at the top. | -| `PushI64` | `0x14` | `val: i64` | `[] -> [i64]` | Pushes an immediate 64-bit integer. | -| `PushF64` | `0x15` | `val: f64` | `[] -> [f64]` | Pushes an immediate 64-bit floating point. | -| `PushBool` | `0x16` | `val: u8` | `[] -> [bool]` | Pushes a boolean (0=false, 1=true). | -| `PushI32` | `0x17` | `val: i32` | `[] -> [i32]` | Pushes an immediate 32-bit integer. | -| `PopN` | `0x18` | `n: u16` | `[...] -> [...]` | Removes `n` values from the stack at once. | - -### 6.3 Arithmetic -The VM performs automatic type promotion (e.g., `i32` + `f64` results in `f64`). - -| OpCode | Value | Stack | Description | -| :--- | :--- | :--- | :--- | -| `Add` | `0x20` | `[a, b] -> [a + b]` | Adds the two top values. | -| `Sub` | `0x21` | `[a, b] -> [a - b]` | Subtracts `b` from `a`. | -| `Mul` | `0x22` | `[a, b] -> [a * b]` | Multiplies the two top values. | -| `Div` | `0x23` | `[a, b] -> [a / b]` | Divides `a` by `b`. Error if `b == 0`. | -| `Neg` | `0x3E` | `[a] -> [-a]` | Negates the numeric sign. | - -### 6.4 Logic and Comparison - -| OpCode | Value | Stack | Description | -| :--- | :--- | :--- | :--- | -| `Eq` | `0x30` | `[a, b] -> [bool]` | Tests equality. | -| `Neq` | `0x31` | `[a, b] -> [bool]` | Tests inequality. | -| `Lt` | `0x32` | `[a, b] -> [bool]` | `a < b` | -| `Gt` | `0x33` | `[a, b] -> [bool]` | `a > b` | -| `Lte` | `0x3C` | `[a, b] -> [bool]` | `a <= b` | -| `Gte` | `0x3D` | `[a, b] -> [bool]` | `a >= b` | -| `And` | `0x34` | `[a, b] -> [bool]` | Logical AND (boolean). | -| `Or` | `0x35` | `[a, b] -> [bool]` | Logical OR (boolean). | -| `Not` | `0x36` | `[a] -> [!a]` | Logical NOT. | -| `BitAnd` | `0x37` | `[a, b] -> [int]` | Bitwise AND. | -| `BitOr` | `0x38` | `[a, b] -> [int]` | Bitwise OR. | -| `BitXor` | `0x39` | `[a, b] -> [int]` | Bitwise XOR. | -| `Shl` | `0x3A` | `[a, b] -> [int]` | Shift Left: `a << b`. | -| `Shr` | `0x3B` | `[a, b] -> [int]` | Shift Right: `a >> b`. | - -### 6.5 Variables and Memory - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `GetGlobal`| `0x40` | `idx: u32` | `[] -> [val]` | Loads value from global `idx`. | -| `SetGlobal`| `0x41` | `idx: u32` | `[val] -> []` | Stores top of stack in global `idx`. | -| `GetLocal` | `0x42` | `idx: u32` | `[] -> [val]` | Loads local `idx` from the current frame. | -| `SetLocal` | `0x43` | `idx: u32` | `[val] -> []` | Stores top of stack in local `idx` of the current frame. | - -### 6.6 Functions and Scope - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `Call` | `0x50` | `addr: u32, args: u32` | `[a1, a2] -> [...]` | Calls `addr`. The `args` values at the top become locals of the new frame. | -| `Ret` | `0x51` | - | `[val] -> [val]` | Returns from the current function, clearing the frame and returning the top value. | -| `PushScope`| `0x52` | - | `[] -> []` | Starts a sub-scope (block) for temporary locals. | -| `PopScope` | `0x53` | - | `[] -> []` | Ends sub-scope, removing locals created in it from the stack. | - -### 6.7 Heap (Dynamic Memory) - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `Alloc` | `0x60` | `size: u32` | `[] -> [ref]` | Allocates `size` slots on the heap and returns a reference. | -| `LoadRef` | `0x61` | `offset: u32`| `[ref] -> [val]` | Reads value from the heap at address `ref + offset`. | -| `StoreRef`| `0x62` | `offset: u32`| `[ref, val] -> []` | Writes `val` to the heap at address `ref + offset`. | - -### 6.8 System and Synchronization - -| OpCode | Value | Operands | Stack | Description | -| :--- | :--- | :--- | :--- | :--- | -| `Syscall` | `0x70` | `id: u32` | `[...] -> [...]` | Invokes a system/firmware function. The stack depends on the syscall. | -| `FrameSync`| `0x80` | - | `[] -> []` | Marks the end of processing for the current logical frame (60 FPS). | - ---- - -## PBC Structure (Prometeu ByteCode) - -PBC is the official binary format for Prometeu programs. - -```rust -// Example of how to load a PBC file -let bytes = std::fs::read("game.pbc")?; -let pbc = prometeu_bytecode::pbc::parse_pbc(&bytes)?; - -println!("ROM size: {} bytes", pbc.rom.len()); -println!("Constants: {}", pbc.cp.len()); -``` - ---- - -## Assembler and Disassembler - -This crate provides tools to facilitate code generation and inspection. - -### Assembly (Assembler) -```rust -use prometeu_bytecode::asm::{assemble, Asm, Operand}; -use prometeu_bytecode::opcode::OpCode; - -let instructions = vec![ - Asm::Op(OpCode::PushI32, vec![Operand::I32(10)]), - Asm::Op(OpCode::PushI32, vec![Operand::I32(20)]), - Asm::Op(OpCode::Add, vec![]), - Asm::Op(OpCode::Halt, vec![]), -]; - -let rom_bytes = assemble(&instructions).unwrap(); -``` - -### Disassembly (Disassembler) -```rust -use prometeu_bytecode::disasm::disasm; - -let rom = vec![/* ... bytes ... */]; -let code = disasm(&rom); - -for instr in code { - println!("{:04X}: {:?} {:?}", instr.pc, instr.opcode, instr.operands); -} -``` diff --git a/crates/compiler/prometeu-bytecode/src/abi.rs b/crates/compiler/prometeu-bytecode/src/abi.rs deleted file mode 100644 index 613161e1..00000000 --- a/crates/compiler/prometeu-bytecode/src/abi.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! This module defines the Application Binary Interface (ABI) of the Prometeu Virtual Machine. -//! It specifies how instructions are encoded in bytes and how they interact with memory. - -use crate::opcode::OpCode; -use crate::opcode_spec::OpCodeSpecExt; - -/// Returns the size in bytes of the operands for a given OpCode. -/// -/// Note: This does NOT include the 2 bytes of the OpCode itself. -/// For example, `PushI32` has a size of 4, but occupies 6 bytes in ROM (2 for OpCode + 4 for value). -pub fn operand_size(opcode: OpCode) -> usize { - opcode.spec().imm_bytes as usize -} - -// --- 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; -/// The syscall ID provided is not recognized by the system. -pub const TRAP_INVALID_SYSCALL: u32 = 0x0000_0007; -/// Not enough arguments on the stack for the requested syscall. -pub const TRAP_STACK_UNDERFLOW: u32 = 0x0000_0008; -/// Attempted to access a local slot that is out of bounds for the current frame. -pub const TRAP_INVALID_LOCAL: u32 = 0x0000_0009; -/// Division or modulo by zero. -pub const TRAP_DIV_ZERO: u32 = 0x0000_000A; -/// Attempted to call a function that does not exist in the function table. -pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B; -/// Executed RET with an incorrect stack height (mismatch with function metadata). -pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C; - -use serde::{Deserialize, Serialize}; - -/// Detailed information about a source code span. -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] -pub struct SourceSpan { - pub file_id: u32, - pub start: u32, - pub end: u32, -} - -/// 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, - /// Optional source span information if debug symbols are available. - pub span: Option, -} - -/// Checks if an instruction is a jump (branch) instruction. -pub fn is_jump(opcode: OpCode) -> bool { - opcode.spec().is_branch -} - -/// Checks if an instruction has any immediate operands in the instruction stream. -pub fn has_immediate(opcode: OpCode) -> bool { - opcode.spec().imm_bytes > 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); - assert_eq!(TRAP_INVALID_SYSCALL, 0x07); - assert_eq!(TRAP_STACK_UNDERFLOW, 0x08); - assert_eq!(TRAP_INVALID_LOCAL, 0x09); - assert_eq!(TRAP_DIV_ZERO, 0x0A); - assert_eq!(TRAP_INVALID_FUNC, 0x0B); - assert_eq!(TRAP_BAD_RET_SLOTS, 0x0C); - } - - #[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. - -System Traps: -- INVALID_SYSCALL (0x07): Unknown syscall ID. -- STACK_UNDERFLOW (0x08): Missing syscall arguments. -- INVALID_LOCAL (0x09): Local slot out of bounds. -- DIV_ZERO (0x0A): Division by zero. -- INVALID_FUNC (0x0B): Function table index out of bounds. -- BAD_RET_SLOTS (0x0C): Stack height mismatch at RET. - -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\nSystem Traps:\n- INVALID_SYSCALL (0x{:02X}): Unknown syscall ID.\n- STACK_UNDERFLOW (0x{:02X}): Missing syscall arguments.\n- INVALID_LOCAL (0x{:02X}): Local slot out of bounds.\n- DIV_ZERO (0x{:02X}): Division by zero.\n- INVALID_FUNC (0x{:02X}): Function table index out of bounds.\n- BAD_RET_SLOTS (0x{:02X}): Stack height mismatch at RET.\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, - TRAP_INVALID_SYSCALL, TRAP_STACK_UNDERFLOW, TRAP_INVALID_LOCAL, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_BAD_RET_SLOTS, - operand_size(OpCode::Alloc), - operand_size(OpCode::GateLoad), - operand_size(OpCode::GateStore), - operand_size(OpCode::PopN) - ); - - assert_eq!(current_info.trim(), abi_info.trim()); - } - - #[test] - fn operand_size_matches_spec_for_all_opcodes() { - // Scan a generous numeric range and validate every decodable opcode. - // This ensures that abi::operand_size (if kept) never diverges from the - // canonical spec (OpcodeSpec::imm_bytes). - for raw in 0u16..=1023u16 { - if let Ok(op) = OpCode::try_from(raw) { - assert_eq!( - operand_size(op), - op.spec().imm_bytes as usize, - "operand_size must match spec for {:?}", - op - ); - } - } - } -} diff --git a/crates/compiler/prometeu-bytecode/src/asm.rs b/crates/compiler/prometeu-bytecode/src/asm.rs deleted file mode 100644 index 76a43265..00000000 --- a/crates/compiler/prometeu-bytecode/src/asm.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::opcode::OpCode; -use crate::readwrite::*; -use std::collections::HashMap; - -/// Represents an operand for an instruction. -#[derive(Debug, Clone)] -pub enum Operand { - /// 32-bit unsigned integer (e.g., indices, addresses). - U32(u32), - /// 32-bit signed integer. - I32(i32), - /// 64-bit signed integer. - I64(i64), - /// 64-bit floating point. - F64(f64), - /// Boolean (true/false). - Bool(bool), - /// A symbolic label that will be resolved to an absolute PC address. - Label(String), - /// A symbolic label that will be resolved to a PC address relative to another label. - RelLabel(String, String), -} - -/// Represents an assembly-level element (either an instruction or a label). -#[derive(Debug, Clone)] -pub enum Asm { - /// An OpCode followed by its operands. The mnemonics represent the operation to be performed. - Op(OpCode, Vec), - /// A named marker in the code (e.g., "start:"). - Label(String), -} - -/// Internal helper to calculate the next PC based on the operands' sizes. -pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec) -> u32 { - let mut pcp: u32 = initial_pc; - for operand in operands { - match operand { - Operand::U32(_) | Operand::I32(_) | Operand::Label(_) | Operand::RelLabel(_, _) => pcp += 4, - Operand::I64(_) | Operand::F64(_) => pcp += 8, - Operand::Bool(_) => pcp += 1, - } - } - pcp -} - -pub struct AssembleResult { - pub code: Vec, - pub unresolved_labels: HashMap>, -} - -/// Converts a list of assembly instructions into raw ROM bytes. -/// -/// The assembly process is done in two passes: -/// 1. **Label Resolution**: Iterates through all instructions to calculate the PC (Program Counter) -/// of each label and stores them in a map. -/// 2. **Code Generation**: Translates each OpCode and its operands (resolving labels using the map) -/// into the final binary format. -pub fn assemble(instructions: &[Asm]) -> Result, String> { - let res = assemble_with_unresolved(instructions)?; - if !res.unresolved_labels.is_empty() { - let labels: Vec<_> = res.unresolved_labels.keys().cloned().collect(); - return Err(format!("Undefined labels: {:?}", labels)); - } - Ok(res.code) -} - -pub fn assemble_with_unresolved(instructions: &[Asm]) -> Result { - let mut labels = HashMap::new(); - let mut current_pc = 0u32; - - // First pass: resolve labels - for instr in instructions { - match instr { - Asm::Label(name) => { - labels.insert(name.clone(), current_pc); - } - Asm::Op(_opcode, operands) => { - current_pc += 2; // OpCode is u16 (2 bytes) - current_pc = update_pc_by_operand(current_pc, operands); - } - } - } - - // Second pass: generate bytes - let mut rom = Vec::new(); - let mut unresolved_labels: HashMap> = HashMap::new(); - let mut pc = 0u32; - - for instr in instructions { - match instr { - Asm::Label(_) => {} - Asm::Op(opcode, operands) => { - write_u16_le(&mut rom, *opcode as u16).map_err(|e| e.to_string())?; - pc += 2; - for operand in operands { - match operand { - Operand::U32(v) => { - write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?; - pc += 4; - } - Operand::I32(v) => { - write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?; - pc += 4; - } - Operand::I64(v) => { - write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?; - pc += 8; - } - Operand::F64(v) => { - write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?; - pc += 8; - } - Operand::Bool(v) => { - rom.push(if *v { 1 } else { 0 }); - pc += 1; - } - Operand::Label(name) => { - if let Some(addr) = labels.get(name) { - write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?; - } else { - unresolved_labels.entry(name.clone()).or_default().push(pc); - write_u32_le(&mut rom, 0).map_err(|e| e.to_string())?; // Placeholder - } - pc += 4; - } - Operand::RelLabel(name, base) => { - let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?; - let base_addr = labels.get(base).ok_or(format!("Undefined base label: {}", base))?; - let rel_addr = (*addr as i64) - (*base_addr as i64); - write_u32_le(&mut rom, rel_addr as u32).map_err(|e| e.to_string())?; - pc += 4; - } - } - } - } - } - } - - Ok(AssembleResult { - code: rom, - unresolved_labels, - }) -} diff --git a/crates/compiler/prometeu-bytecode/src/disasm.rs b/crates/compiler/prometeu-bytecode/src/disasm.rs deleted file mode 100644 index 4383182e..00000000 --- a/crates/compiler/prometeu-bytecode/src/disasm.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::opcode::OpCode; -use crate::readwrite::*; -use std::io::{Cursor, Read}; - -/// Represents a disassembled instruction. -#[derive(Debug, Clone)] -pub struct Instr { - /// The absolute address (PC) where this instruction starts in ROM. - pub pc: u32, - /// The decoded OpCode. - pub opcode: OpCode, - /// The list of operands extracted from the byte stream. - pub operands: Vec, -} - -/// Represents an operand decoded from the byte stream. -#[derive(Debug, Clone)] -pub enum DisasmOperand { - U32(u32), - I32(i32), - I64(i64), - F64(f64), - Bool(bool), -} - -/// Translates raw ROM bytes back into a human-readable list of instructions. -/// -/// This is the inverse of the assembly process. It reads the bytes sequentially, -/// identifies the OpCode, and then reads the correct number of operand bytes -/// based on the ISA rules. -pub fn disasm(rom: &[u8]) -> Result, String> { - let mut instructions = Vec::new(); - let mut cursor = Cursor::new(rom); - - while (cursor.position() as usize) < rom.len() { - let pc = cursor.position() as u32; - let op_val = read_u16_le(&mut cursor).map_err(|e| e.to_string())?; - let opcode = OpCode::try_from(op_val)?; - let mut operands = Vec::new(); - - match opcode { - OpCode::PushConst | OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue - | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal - | OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore | OpCode::Call => { - let v = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; - operands.push(DisasmOperand::U32(v)); - } - OpCode::PushI64 | OpCode::PushF64 => { - let v = read_i64_le(&mut cursor).map_err(|e| e.to_string())?; - operands.push(DisasmOperand::I64(v)); - } - OpCode::PushBool => { - let mut b_buf = [0u8; 1]; - cursor.read_exact(&mut b_buf).map_err(|e| e.to_string())?; - operands.push(DisasmOperand::Bool(b_buf[0] != 0)); - } - OpCode::Alloc => { - let v1 = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; - let v2 = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; - operands.push(DisasmOperand::U32(v1)); - operands.push(DisasmOperand::U32(v2)); - } - _ => {} - } - - instructions.push(Instr { pc, opcode, operands }); - } - - Ok(instructions) -} diff --git a/crates/compiler/prometeu-bytecode/src/io.rs b/crates/compiler/prometeu-bytecode/src/io.rs deleted file mode 100644 index def3b6b5..00000000 --- a/crates/compiler/prometeu-bytecode/src/io.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub fn read_u32_le(buf: &[u8], pos: usize) -> Option { - let b = buf.get(pos..pos + 4)?; - Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]])) -} - -pub fn write_u32_le(buf: &mut [u8], pos: usize, v: u32) -> Option<()> { - let b = buf.get_mut(pos..pos + 4)?; - let le = v.to_le_bytes(); - b.copy_from_slice(&le); - Some(()) -} diff --git a/crates/compiler/prometeu-bytecode/src/layout.rs b/crates/compiler/prometeu-bytecode/src/layout.rs deleted file mode 100644 index 4f48973b..00000000 --- a/crates/compiler/prometeu-bytecode/src/layout.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! Shared bytecode layout utilities, used by both compiler (emitter/linker) -//! and the VM (verifier/loader). This ensures a single source of truth for -//! how function ranges, instruction boundaries, and pc→function lookups are -//! interpreted post-link. - -use crate::decoder::decode_next; -use crate::FunctionMeta; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct FunctionLayout { - pub start: usize, - pub end: usize, // exclusive -} - -/// Precompute canonical [start, end) ranges for all functions. -/// -/// Contract: -/// - Ranges are computed by sorting functions by `code_offset` (stable), -/// then using the next function's start as the current end; the last -/// function ends at `code_len_total`. -/// - The returned vector is indexed by the original function indices. -pub fn compute_function_layouts(functions: &[FunctionMeta], code_len_total: usize) -> Vec { - // Build index array and sort by start offset (stable to preserve relative order). - let mut idxs: Vec = (0..functions.len()).collect(); - idxs.sort_by_key(|&i| functions[i].code_offset as usize); - - // Optional guard: offsets should be strictly increasing (duplicates are suspicious). - for w in idxs.windows(2) { - if let [a, b] = *w { - let sa = functions[a].code_offset as usize; - let sb = functions[b].code_offset as usize; - debug_assert!(sa < sb, "Function code_offset must be strictly increasing: {} vs {} (indices {} and {})", sa, sb, a, b); - } - } - - let mut out = vec![FunctionLayout { start: 0, end: 0 }; functions.len()]; - for (pos, &i) in idxs.iter().enumerate() { - let start = functions[i].code_offset as usize; - let end = if pos + 1 < idxs.len() { - functions[idxs[pos + 1]].code_offset as usize - } else { - code_len_total - }; - out[i] = FunctionLayout { start, end }; - } - out -} - -/// Recomputes all `code_len` values in place from the next function start -/// (exclusive end), using the combined code buffer length for the last one. -pub fn recompute_function_lengths_in_place(functions: &mut [FunctionMeta], code_len_total: usize) { - let layouts = compute_function_layouts(functions, code_len_total); - for i in 0..functions.len() { - let start = layouts[i].start; - let end = layouts[i].end; - functions[i].code_len = end.saturating_sub(start) as u32; - } -} - -/// Finds the function index that contains `pc_abs` (absolute), using the -/// canonical ranges (end = next start, exclusive). Returns `None` if none. -pub fn function_index_by_pc(functions: &[FunctionMeta], code_len_total: usize, pc_abs: usize) -> Option { - let layouts = compute_function_layouts(functions, code_len_total); - for i in 0..functions.len() { - let start = layouts[i].start; - let end = layouts[i].end; - if pc_abs >= start && pc_abs < end { - return Some(i); - } - } - None -} - -/// Alias: canonical function lookup by absolute PC. -#[inline] -pub fn lookup_function_by_pc(functions: &[FunctionMeta], code_len_total: usize, pc_abs: usize) -> Option { - function_index_by_pc(functions, code_len_total, pc_abs) -} - -/// Returns true if `rel_pc` (relative to the function start) is a valid -/// instruction boundary as determined by the canonical decoder. -/// -/// Contract: -/// - `rel_pc == 0` is always a boundary if `func_idx` is valid. -/// - Boundaries are computed by stepping with `decoder::decode_next` from the -/// function start up to (and possibly past) `rel_pc` but never beyond the -/// function exclusive end. -/// - Any decode error before reaching `rel_pc` yields `false` (invalid program). -pub fn is_boundary(functions: &[FunctionMeta], code: &[u8], code_len_total: usize, func_idx: usize, rel_pc: usize) -> bool { - let (start, end) = match functions.get(func_idx) { - Some(_) => { - let layouts = compute_function_layouts(functions, code_len_total); - let l = &layouts[func_idx]; - (l.start, l.end) - } - None => return false, - }; - - let func_len = end.saturating_sub(start); - if rel_pc == 0 { return true; } - if rel_pc > func_len { return false; } - - let target = start + rel_pc; - let mut pc = start; - while pc < end { - match decode_next(pc, code) { - Ok(di) => { - let next = di.next_pc; - if next > end { return false; } - if next == target { return true; } - if next <= pc { return false; } // must make progress - pc = next; - if pc > target { return false; } - } - Err(_) => return false, - } - } - // If we reached end without matching `target`, only boundary is exact end - target == end -} - -/// Returns true if `abs_pc` is a valid instruction boundary for the function -/// containing it, according to the canonical decoder. Returns false if `abs_pc` -/// is not within any function range or if decoding fails. -pub fn is_boundary_abs(functions: &[FunctionMeta], code: &[u8], code_len_total: usize, abs_pc: usize) -> bool { - if let Some(func_idx) = lookup_function_by_pc(functions, code_len_total, abs_pc) { - let layouts = compute_function_layouts(functions, code_len_total); - let (start, _end) = { - let l = &layouts[func_idx]; - (l.start, l.end) - }; - let rel = abs_pc.saturating_sub(start); - return is_boundary(functions, code, code_len_total, func_idx, rel); - } - - // Not inside any function range; allow exact function starts/ends as - // valid boundaries (e.g., last function end == total code len). - let layouts = compute_function_layouts(functions, code_len_total); - for i in 0..functions.len() { - let start = layouts[i].start; - let end = layouts[i].end; - if abs_pc == start || abs_pc == end { - return true; - } - } - false -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::asm::{assemble, Asm, Operand}; - use crate::opcode::OpCode; - - fn build_funcs(offsets: &[usize], lens: Option<&[usize]>) -> Vec { - let mut v = Vec::new(); - for (i, off) in offsets.iter().copied().enumerate() { - let len_u32 = lens.and_then(|ls| ls.get(i).copied()).unwrap_or(0) as u32; - v.push(FunctionMeta { - code_offset: off as u32, - code_len: len_u32, - param_slots: 0, - local_slots: 0, - return_slots: 0, - max_stack_slots: 0, - }); - } - v - } - - #[test] - fn boundaries_known_sequence() { - // Build a function with mixed immediate sizes: - // [NOP][PUSH_I32 4][PUSH_I64 8][PUSH_BOOL 1][HALT] - let code = assemble(&[ - Asm::Op(OpCode::Nop, vec![]), - Asm::Op(OpCode::PushI32, vec![Operand::I32(123)]), - Asm::Op(OpCode::PushI64, vec![Operand::I64(42)]), - Asm::Op(OpCode::PushBool, vec![Operand::Bool(true)]), - Asm::Op(OpCode::Halt, vec![]), - ]).unwrap(); - - // Single function starting at 0 - let code_len_total = code.len(); - let mut funcs = build_funcs(&[0], None); - recompute_function_lengths_in_place(&mut funcs, code_len_total); - - // Expected boundaries (relative): 0, 2, 8, 18, 21, 23 - // Explanation per instruction size: opcode(2) + imm - let expected = [0usize, 2, 8, 18, 21, 23]; - for rel in 0..=expected.last().copied().unwrap() { - let should_be_boundary = expected.contains(&rel); - assert_eq!( - is_boundary(&funcs, &code, code_len_total, 0, rel), - should_be_boundary, - "rel_pc={} boundary mismatch", - rel - ); - } - - // Check absolute variant too - for rel in expected { - let abs = rel; - assert!(is_boundary_abs(&funcs, &code, code_len_total, abs)); - } - } - - #[test] - fn fuzz_table_monotonic_and_boundaries() { - // Build a pseudo-random but valid sequence using a simple pattern over opcodes - // to avoid invalid encodings. We cycle through a few known-good opcodes. - let ops = [ - OpCode::Nop, - OpCode::PushI32, - OpCode::PushBool, - OpCode::PushI64, - OpCode::Pop, - OpCode::Ret, - ]; - - let mut prog = Vec::new(); - for i in 0..50 { - let op = ops[i % ops.len()]; - let asm = match op { - OpCode::Nop => Asm::Op(OpCode::Nop, vec![]), - OpCode::PushI32 => Asm::Op(OpCode::PushI32, vec![Operand::I32(i as i32)]), - OpCode::PushBool => Asm::Op(OpCode::PushBool, vec![Operand::Bool(i % 2 == 0)]), - OpCode::PushI64 => Asm::Op(OpCode::PushI64, vec![Operand::I64(i as i64)]), - OpCode::Pop => Asm::Op(OpCode::Pop, vec![]), - OpCode::Ret => Asm::Op(OpCode::Ret, vec![]), - _ => unreachable!(), - }; - prog.push(asm); - } - - let code = assemble(&prog).unwrap(); - let code_len_total = code.len(); - let mut funcs = build_funcs(&[0], None); - recompute_function_lengths_in_place(&mut funcs, code_len_total); - let layouts = compute_function_layouts(&funcs, code_len_total); - let (start, end) = (layouts[0].start, layouts[0].end); - assert_eq!(start, 0); - assert_eq!(end, code_len_total); - - // Walk with decoder and verify boundaries are accepted - let mut pc = start; - while pc < end { - assert!(is_boundary_abs(&funcs, &code, code_len_total, pc)); - let di = decode_next(pc, &code).expect("decode_next"); - assert!(di.next_pc > pc && di.next_pc <= end); - pc = di.next_pc; - } - // End must be a boundary too - assert!(is_boundary(&funcs, &code, code_len_total, 0, end - start)); - } - - #[test] - fn compute_function_layouts_end_is_next_start() { - // Synthetic functions with known offsets: 0, 10, 25; total_len = 40 - let funcs = build_funcs(&[0, 10, 25], None); - let layouts = compute_function_layouts(&funcs, 40); - - assert_eq!(layouts.len(), 3); - assert_eq!(layouts[0], FunctionLayout { start: 0, end: 10 }); - assert_eq!(layouts[1], FunctionLayout { start: 10, end: 25 }); - assert_eq!(layouts[2], FunctionLayout { start: 25, end: 40 }); - - for i in 0..3 { - let l = &layouts[i]; - assert_eq!(l.end - l.start, (funcs.get(i + 1).map(|n| n.code_offset as usize).unwrap_or(40)) - (funcs[i].code_offset as usize)); - } - } -} diff --git a/crates/compiler/prometeu-bytecode/src/lib.rs b/crates/compiler/prometeu-bytecode/src/lib.rs deleted file mode 100644 index a70fb25c..00000000 --- a/crates/compiler/prometeu-bytecode/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod opcode; -pub mod opcode_spec; -pub mod abi; -pub mod readwrite; -pub mod asm; -pub mod disasm; -pub mod layout; -pub mod decoder; - -mod model; -pub mod io; -pub mod value; -pub mod program_image; - -pub use model::*; -pub use value::Value; diff --git a/crates/compiler/prometeu-bytecode/src/readwrite.rs b/crates/compiler/prometeu-bytecode/src/readwrite.rs deleted file mode 100644 index 6dc198da..00000000 --- a/crates/compiler/prometeu-bytecode/src/readwrite.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Binary I/O utilities for the Prometeu ecosystem. -//! All multi-byte data is encoded in Little-Endian format. - -use std::io::{self, Read, Write}; - -/// Reads a 16-bit unsigned integer from a reader. -pub fn read_u16_le(mut reader: R) -> io::Result { - let mut buf = [0u8; 2]; - reader.read_exact(&mut buf)?; - Ok(u16::from_le_bytes(buf)) -} - -/// Reads a 32-bit unsigned integer from a reader. -pub fn read_u32_le(mut reader: R) -> io::Result { - let mut buf = [0u8; 4]; - reader.read_exact(&mut buf)?; - Ok(u32::from_le_bytes(buf)) -} - -/// Reads a 64-bit signed integer from a reader. -pub fn read_i64_le(mut reader: R) -> io::Result { - let mut buf = [0u8; 8]; - reader.read_exact(&mut buf)?; - Ok(i64::from_le_bytes(buf)) -} - -/// Reads a 64-bit floating point from a reader. -pub fn read_f64_le(mut reader: R) -> io::Result { - let mut buf = [0u8; 8]; - reader.read_exact(&mut buf)?; - Ok(f64::from_le_bytes(buf)) -} - -/// Writes a 16-bit unsigned integer to a writer. -pub fn write_u16_le(mut writer: W, val: u16) -> io::Result<()> { - writer.write_all(&val.to_le_bytes()) -} - -/// Writes a 32-bit unsigned integer to a writer. -pub fn write_u32_le(mut writer: W, val: u32) -> io::Result<()> { - writer.write_all(&val.to_le_bytes()) -} - -/// Writes a 64-bit signed integer to a writer. -pub fn write_i64_le(mut writer: W, val: i64) -> io::Result<()> { - writer.write_all(&val.to_le_bytes()) -} - -/// Writes a 64-bit floating point to a writer. -pub fn write_f64_le(mut writer: W, val: f64) -> io::Result<()> { - writer.write_all(&val.to_le_bytes()) -} diff --git a/crates/compiler/prometeu-bytecode/Cargo.toml b/crates/console/prometeu-bytecode/Cargo.toml similarity index 100% rename from crates/compiler/prometeu-bytecode/Cargo.toml rename to crates/console/prometeu-bytecode/Cargo.toml diff --git a/crates/console/prometeu-bytecode/src/abi.rs b/crates/console/prometeu-bytecode/src/abi.rs new file mode 100644 index 00000000..2b4c9b10 --- /dev/null +++ b/crates/console/prometeu-bytecode/src/abi.rs @@ -0,0 +1,50 @@ +//! This module defines the Application Binary Interface (ABI) of the Prometeu Virtual Machine. +//! It specifies how instructions are encoded in bytes and how they interact with memory. + +// --- 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; +/// The syscall ID provided is not recognized by the system. +pub const TRAP_INVALID_SYSCALL: u32 = 0x0000_0007; +/// Not enough arguments on the stack for the requested syscall. +pub const TRAP_STACK_UNDERFLOW: u32 = 0x0000_0008; +/// Attempted to access a local slot that is out of bounds for the current frame. +pub const TRAP_INVALID_LOCAL: u32 = 0x0000_0009; +/// Division or modulo by zero. +pub const TRAP_DIV_ZERO: u32 = 0x0000_000A; +/// Attempted to call a function that does not exist in the function table. +pub const TRAP_INVALID_FUNC: u32 = 0x0000_000B; +/// Executed RET with an incorrect stack height (mismatch with function metadata). +pub const TRAP_BAD_RET_SLOTS: u32 = 0x0000_000C; + +use serde::{Deserialize, Serialize}; + +/// Detailed information about a source code span. +#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct SourceSpan { + pub file_id: u32, + pub start: u32, + pub end: u32, +} + +/// 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, + /// Optional source span information if debug symbols are available. + pub span: Option, +} \ No newline at end of file diff --git a/crates/compiler/prometeu-bytecode/src/decoder.rs b/crates/console/prometeu-bytecode/src/decoder.rs similarity index 56% rename from crates/compiler/prometeu-bytecode/src/decoder.rs rename to crates/console/prometeu-bytecode/src/decoder.rs index 142a78e7..f787c3cd 100644 --- a/crates/compiler/prometeu-bytecode/src/decoder.rs +++ b/crates/console/prometeu-bytecode/src/decoder.rs @@ -90,7 +90,7 @@ impl<'a> DecodedInstr<'a> { /// Decodes the instruction at program counter `pc` from `bytes`. /// Returns the decoded instruction with canonical `next_pc`. #[inline] -pub fn decode_next<'a>(pc: usize, bytes: &'a [u8]) -> Result, DecodeError> { +pub fn decode_next(pc: usize, bytes: &'_ [u8]) -> Result, DecodeError> { if pc + 2 > bytes.len() { return Err(DecodeError::TruncatedOpcode { pc }); } @@ -118,88 +118,4 @@ pub fn decode_next<'a>(pc: usize, bytes: &'a [u8]) -> Result, D next_pc: imm_end, imm: &bytes[imm_start..imm_end], }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::asm::{assemble, Asm, Operand}; - use crate::opcode::OpCode; - - #[test] - fn decode_basic_no_imm() { - // Encode a NOP (0x0000) - let rom = assemble(&[Asm::Op(OpCode::Nop, vec![])]).unwrap(); - let d = decode_next(0, &rom).unwrap(); - assert_eq!(d.opcode, OpCode::Nop); - assert_eq!(d.pc, 0); - assert_eq!(d.next_pc, 2); - assert_eq!(d.imm.len(), 0); - } - - #[test] - fn decode_with_u32_imm() { - // PUSH_CONST 0x11223344 - let rom = assemble(&[Asm::Op(OpCode::PushConst, vec![Operand::U32(0x11223344)])]).unwrap(); - let d = decode_next(0, &rom).unwrap(); - assert_eq!(d.opcode, OpCode::PushConst); - assert_eq!(d.imm_u32().unwrap(), 0x11223344); - assert_eq!(d.next_pc, 2 + 4); - } - - #[test] - fn decode_with_u8_imm() { - // PUSH_BOOL true - let rom = assemble(&[Asm::Op(OpCode::PushBool, vec![Operand::Bool(true)])]).unwrap(); - let d = decode_next(0, &rom).unwrap(); - assert_eq!(d.opcode, OpCode::PushBool); - assert_eq!(d.imm.len(), 1); - assert_eq!(d.imm_u8().unwrap(), 1); - assert_eq!(d.next_pc, 2 + 1); - } - - #[test] - fn decode_with_i64_and_f64() { - // PUSH_I64, PUSH_F64 - let rom = assemble(&[ - Asm::Op(OpCode::PushI64, vec![Operand::I64(-123)]), - Asm::Op(OpCode::PushF64, vec![Operand::F64(3.25)]), - ]).unwrap(); - - let d0 = decode_next(0, &rom).unwrap(); - assert_eq!(d0.opcode, OpCode::PushI64); - assert_eq!(d0.imm_i64().unwrap(), -123); - - let d1 = decode_next(d0.next_pc, &rom).unwrap(); - assert_eq!(d1.opcode, OpCode::PushF64); - assert!((d1.imm_f64().unwrap() - 3.25).abs() < 1e-12); - } - - #[test] - fn decode_truncated() { - let rom: Vec = vec![0x00, 0x00]; // NOP complete - assert!(matches!(decode_next(1, &rom), Err(DecodeError::TruncatedOpcode { .. }))); - } - - #[test] - fn roundtrip_encode_decode_table() { - let rom = assemble(&[ - Asm::Op(OpCode::Nop, vec![]), - Asm::Op(OpCode::PushConst, vec![Operand::U32(7)]), - Asm::Op(OpCode::Jmp, vec![Operand::U32(4)]), - Asm::Op(OpCode::PushI64, vec![Operand::I64(42)]), - Asm::Op(OpCode::Halt, vec![]), - ]).unwrap(); - - let mut pc = 0usize; - let mut decoded = Vec::new(); - while pc < rom.len() { - let d = decode_next(pc, &rom).unwrap(); - decoded.push(d.opcode); - pc = d.next_pc; - } - - assert_eq!(decoded, vec![OpCode::Nop, OpCode::PushConst, OpCode::Jmp, OpCode::PushI64, OpCode::Halt]); - assert_eq!(pc, rom.len()); - } -} +} \ No newline at end of file diff --git a/crates/console/prometeu-bytecode/src/layout.rs b/crates/console/prometeu-bytecode/src/layout.rs new file mode 100644 index 00000000..cf2f94dc --- /dev/null +++ b/crates/console/prometeu-bytecode/src/layout.rs @@ -0,0 +1,84 @@ +//! Shared bytecode layout utilities, used by both compiler (emitter/linker) +//! and the VM (verifier/loader). This ensures a single source of truth for +//! how function ranges, instruction boundaries, and pc→function lookups are +//! interpreted post-link. + +use crate::model::FunctionMeta; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FunctionLayout { + pub start: usize, + pub end: usize, // exclusive +} + +/// Precompute canonical [start, end) ranges for all functions. +/// +/// Contract: +/// - Ranges are computed by sorting functions by `code_offset` (stable), +/// then using the next function's start as the current end; the last +/// function ends at `code_len_total`. +/// - The returned vector is indexed by the original function indices. +pub fn compute_function_layouts(functions: &[FunctionMeta], code_len_total: usize) -> Vec { + // Build index array and sort by start offset (stable to preserve relative order). + let mut idxs: Vec = (0..functions.len()).collect(); + idxs.sort_by_key(|&i| functions[i].code_offset as usize); + + // Optional guard: offsets should be strictly increasing (duplicates are suspicious). + for w in idxs.windows(2) { + if let [a, b] = *w { + let sa = functions[a].code_offset as usize; + let sb = functions[b].code_offset as usize; + debug_assert!(sa < sb, "Function code_offset must be strictly increasing: {} vs {} (indices {} and {})", sa, sb, a, b); + } + } + + let mut out = vec![FunctionLayout { start: 0, end: 0 }; functions.len()]; + for (pos, &i) in idxs.iter().enumerate() { + let start = functions[i].code_offset as usize; + let end = if pos + 1 < idxs.len() { + functions[idxs[pos + 1]].code_offset as usize + } else { + code_len_total + }; + out[i] = FunctionLayout { start, end }; + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn build_funcs(offsets: &[usize], lens: Option<&[usize]>) -> Vec { + let mut v = Vec::new(); + for (i, off) in offsets.iter().copied().enumerate() { + let len_u32 = lens.and_then(|ls| ls.get(i).copied()).unwrap_or(0) as u32; + v.push(FunctionMeta { + code_offset: off as u32, + code_len: len_u32, + param_slots: 0, + local_slots: 0, + return_slots: 0, + max_stack_slots: 0, + }); + } + v + } + + #[test] + fn compute_function_layouts_end_is_next_start() { + // Synthetic functions with known offsets: 0, 10, 25; total_len = 40 + let funcs = build_funcs(&[0, 10, 25], None); + let layouts = compute_function_layouts(&funcs, 40); + + assert_eq!(layouts.len(), 3); + assert_eq!(layouts[0], FunctionLayout { start: 0, end: 10 }); + assert_eq!(layouts[1], FunctionLayout { start: 10, end: 25 }); + assert_eq!(layouts[2], FunctionLayout { start: 25, end: 40 }); + + for i in 0..3 { + let l = &layouts[i]; + assert_eq!(l.end - l.start, (funcs.get(i + 1).map(|n| n.code_offset as usize).unwrap_or(40)) - (funcs[i].code_offset as usize)); + } + } +} diff --git a/crates/console/prometeu-bytecode/src/lib.rs b/crates/console/prometeu-bytecode/src/lib.rs new file mode 100644 index 00000000..2e93638d --- /dev/null +++ b/crates/console/prometeu-bytecode/src/lib.rs @@ -0,0 +1,33 @@ +mod opcode; +mod opcode_spec; +mod abi; +mod layout; +mod decoder; +mod model; +mod value; +mod program_image; + +pub use abi::{ + TrapInfo, + TRAP_INVALID_LOCAL, + TRAP_OOB, + TRAP_TYPE, + TRAP_BAD_RET_SLOTS, + TRAP_DEAD_GATE, + TRAP_DIV_ZERO, + TRAP_INVALID_FUNC, + TRAP_INVALID_GATE, + TRAP_STACK_UNDERFLOW, + TRAP_INVALID_SYSCALL, +}; +pub use model::{ + BytecodeLoader, + FunctionMeta, + LoadError, +}; +pub use value::Value; +pub use opcode_spec::OpCodeSpecExt; +pub use opcode::OpCode; +pub use decoder::{decode_next, DecodeError}; +pub use layout::{compute_function_layouts, FunctionLayout}; +pub use program_image::ProgramImage; diff --git a/crates/compiler/prometeu-bytecode/src/model.rs b/crates/console/prometeu-bytecode/src/model.rs similarity index 100% rename from crates/compiler/prometeu-bytecode/src/model.rs rename to crates/console/prometeu-bytecode/src/model.rs diff --git a/crates/compiler/prometeu-bytecode/src/opcode.rs b/crates/console/prometeu-bytecode/src/opcode.rs similarity index 83% rename from crates/compiler/prometeu-bytecode/src/opcode.rs rename to crates/console/prometeu-bytecode/src/opcode.rs index f8ae69f0..21e3e6c5 100644 --- a/crates/compiler/prometeu-bytecode/src/opcode.rs +++ b/crates/console/prometeu-bytecode/src/opcode.rs @@ -354,61 +354,3 @@ impl OpCode { } } } - -#[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); - } -} diff --git a/crates/compiler/prometeu-bytecode/src/opcode_spec.rs b/crates/console/prometeu-bytecode/src/opcode_spec.rs similarity index 100% rename from crates/compiler/prometeu-bytecode/src/opcode_spec.rs rename to crates/console/prometeu-bytecode/src/opcode_spec.rs diff --git a/crates/compiler/prometeu-bytecode/src/program_image.rs b/crates/console/prometeu-bytecode/src/program_image.rs similarity index 98% rename from crates/compiler/prometeu-bytecode/src/program_image.rs rename to crates/console/prometeu-bytecode/src/program_image.rs index 6fbc02c5..b045904e 100644 --- a/crates/compiler/prometeu-bytecode/src/program_image.rs +++ b/crates/console/prometeu-bytecode/src/program_image.rs @@ -1,7 +1,7 @@ use crate::abi::TrapInfo; use std::collections::HashMap; use std::sync::Arc; -use crate::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; +use crate::model::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta}; use crate::value::Value; /// Represents a fully linked, executable PBS program image. diff --git a/crates/compiler/prometeu-bytecode/src/value.rs b/crates/console/prometeu-bytecode/src/value.rs similarity index 100% rename from crates/compiler/prometeu-bytecode/src/value.rs rename to crates/console/prometeu-bytecode/src/value.rs diff --git a/crates/console/prometeu-drivers/README.md b/crates/console/prometeu-drivers/README.md deleted file mode 100644 index 82830274..00000000 --- a/crates/console/prometeu-drivers/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# PROMETEU Core - -This crate contains the core library that defines the behavior of PROMETEU's hardware and software. - -## Responsibilities - -- **Logical Hardware**: Definition of GFX, Input, Audio, and Touch. -- **Virtual Machine**: Execution of custom bytecode (PBC - Prometeu Byte Code). -- **Virtual FS**: File management and "cartridge" access. -- **Firmware/OS**: The internal operating system that manages the application lifecycle, splash screens, and the Hub Home. - -## Architecture - -The core is designed to be deterministic and portable. It does not make direct calls to the host operating system; instead, it defines traits that hosts must implement (e.g., `FsBackend`). diff --git a/crates/console/prometeu-hal/Cargo.toml b/crates/console/prometeu-hal/Cargo.toml index db32d392..c1217ecf 100644 --- a/crates/console/prometeu-hal/Cargo.toml +++ b/crates/console/prometeu-hal/Cargo.toml @@ -5,6 +5,6 @@ edition = "2024" license.workspace = true [dependencies] -prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } +prometeu-bytecode = { path = "../prometeu-bytecode" } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" \ No newline at end of file diff --git a/crates/console/prometeu-hal/src/host_return.rs b/crates/console/prometeu-hal/src/host_return.rs index 41e32b6f..b5222bbe 100644 --- a/crates/console/prometeu-hal/src/host_return.rs +++ b/crates/console/prometeu-hal/src/host_return.rs @@ -1,4 +1,4 @@ -use prometeu_bytecode::Value; +use prometeu_bytecode::{Value, TRAP_OOB}; use crate::vm_fault::VmFault; pub struct HostReturn<'a> { @@ -17,7 +17,7 @@ impl<'a> HostReturn<'a> { } pub fn push_bounded(&mut self, v: u32) -> Result<(), VmFault> { if v > 0xFFFF { - return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_OOB, "Bounded value overflow".into())); + return Err(VmFault::Trap(TRAP_OOB, "Bounded value overflow".into())); } self.stack.push(Value::Bounded(v)); Ok(()) diff --git a/crates/console/prometeu-hal/src/native_helpers.rs b/crates/console/prometeu-hal/src/native_helpers.rs index df0154ab..6cb7a2c4 100644 --- a/crates/console/prometeu-hal/src/native_helpers.rs +++ b/crates/console/prometeu-hal/src/native_helpers.rs @@ -1,4 +1,4 @@ -use prometeu_bytecode::Value; +use prometeu_bytecode::{Value, TRAP_TYPE}; use crate::vm_fault::VmFault; pub fn expect_bounded(args: &[Value], idx: usize) -> Result { @@ -7,13 +7,13 @@ pub fn expect_bounded(args: &[Value], idx: usize) -> Result { Value::Bounded(b) => Some(*b), _ => None, }) - .ok_or_else(|| VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, format!("Expected bounded at index {}", idx))) + .ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Expected bounded at index {}", idx))) } pub fn expect_int(args: &[Value], idx: usize) -> Result { args.get(idx) .and_then(|v| v.as_integer()) - .ok_or_else(|| VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, format!("Expected integer at index {}", idx))) + .ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Expected integer at index {}", idx))) } pub fn expect_bool(args: &[Value], idx: usize) -> Result { @@ -22,5 +22,5 @@ pub fn expect_bool(args: &[Value], idx: usize) -> Result { Value::Boolean(b) => Some(*b), _ => None, }) - .ok_or_else(|| VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, format!("Expected boolean at index {}", idx))) + .ok_or_else(|| VmFault::Trap(TRAP_TYPE, format!("Expected boolean at index {}", idx))) } diff --git a/crates/console/prometeu-system/Cargo.toml b/crates/console/prometeu-system/Cargo.toml index 4fcb329f..18471e35 100644 --- a/crates/console/prometeu-system/Cargo.toml +++ b/crates/console/prometeu-system/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true [dependencies] serde_json = "1.0.149" prometeu-vm = { path = "../prometeu-vm" } -prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } +prometeu-bytecode = { path = "../prometeu-bytecode" } prometeu-hal = { path = "../prometeu-hal" } [dev-dependencies] diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime.rs b/crates/console/prometeu-system/src/virtual_machine_runtime.rs index f24b8f34..0dae3087 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime.rs @@ -5,7 +5,7 @@ use prometeu_hal::log::{LogLevel, LogService, LogSource}; use prometeu_hal::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; use std::collections::HashMap; use std::time::Instant; -use prometeu_bytecode::Value; +use prometeu_bytecode::{Value, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_TYPE}; use prometeu_hal::{expect_bool, expect_int, HostContext, HostReturn, NativeInterface, SyscallId}; use prometeu_hal::asset::{BankType, LoadStatus, SlotRef}; use prometeu_hal::button::Button; @@ -352,7 +352,7 @@ impl VirtualMachineRuntime { 2 => LogLevel::Info, 3 => LogLevel::Warn, 4 => LogLevel::Error, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, format!("Invalid log level: {}", level_val))), + _ => return Err(VmFault::Trap(TRAP_TYPE, format!("Invalid log level: {}", level_val))), }; let app_id = self.current_app_id; @@ -422,427 +422,6 @@ impl VirtualMachineRuntime { } } -#[cfg(test)] -mod tests { - use prometeu_bytecode::Value; - use prometeu_drivers::hardware::Hardware; - use prometeu_hal::{HostReturn, InputSignals}; - use super::*; - - fn call_syscall(os: &mut VirtualMachineRuntime, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result<(), VmFault> { - let args_count = Syscall::from_u32(id).expect(&format!("Invalid syscall id: 0x{:08X}", id)).args_count(); - let mut args = Vec::new(); - for _ in 0..args_count { - // Protege contra underflow/erros de pilha durante testes - match vm.pop() { - Ok(v) => args.push(v), - Err(e) => return Err(VmFault::Panic(e)), - } - } - args.reverse(); - let mut ret = HostReturn::new(&mut vm.operand_stack); - os.syscall(id, &args, &mut ret, &mut HostContext::new(Some(hw))) - } - - #[test] - fn test_infinite_loop_budget_reset_bug() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - let signals = InputSignals::default(); - - let rom = prometeu_bytecode::BytecodeModule { - version: 0, - const_pool: vec![], - functions: vec![prometeu_bytecode::FunctionMeta { - code_offset: 0, - code_len: 6, - param_slots: 0, - local_slots: 0, - return_slots: 0, - max_stack_slots: 0, - }], - code: vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00], - debug_info: None, - exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }], - }.serialize(); - let cartridge = Cartridge { - app_id: 1234, - title: "test".to_string(), - app_version: "1.0.0".to_string(), - app_mode: AppMode::Game, - entrypoint: "0".to_string(), - program: rom, - assets: vec![], - asset_table: vec![], - preload: vec![], - }; - os.initialize_vm(&mut vm, &cartridge); - - // First tick - os.tick(&mut vm, &signals, &mut hw); - let cycles_after_tick_1 = vm.cycles; - assert!(cycles_after_tick_1 >= VirtualMachineRuntime::CYCLES_PER_LOGICAL_FRAME); - - // Second tick - Now it SHOULD NOT gain more budget - os.tick(&mut vm, &signals, &mut hw); - let cycles_after_tick_2 = vm.cycles; - - // FIX: It should not have consumed cycles in the second tick because the logical frame budget ended - println!("Cycles after tick 1: {}, tick 2: {}", cycles_after_tick_1, cycles_after_tick_2); - assert_eq!(cycles_after_tick_2, cycles_after_tick_1, "VM should NOT have consumed more cycles in the second tick because logical frame budget is exhausted"); - } - - #[test] - fn test_budget_reset_on_frame_sync() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - let signals = InputSignals::default(); - - // Loop that calls FrameSync: - // PUSH_CONST 0 (dummy) - // FrameSync (0x80) - // JMP 0 - let rom = prometeu_bytecode::BytecodeModule { - version: 0, - const_pool: vec![], - functions: vec![prometeu_bytecode::FunctionMeta { - code_offset: 0, - code_len: 8, - param_slots: 0, - local_slots: 0, - return_slots: 0, - max_stack_slots: 0, - }], - code: vec![ - 0x80, 0x00, // FrameSync (2 bytes opcode) - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // JMP 0 (2 bytes opcode + 4 bytes u32) - ], - debug_info: None, - exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }], - }.serialize(); - let cartridge = Cartridge { - app_id: 1234, - title: "test".to_string(), - app_version: "1.0.0".to_string(), - app_mode: AppMode::Game, - entrypoint: "0".to_string(), - program: rom, - assets: vec![], - asset_table: vec![], - preload: vec![], - }; - os.initialize_vm(&mut vm, &cartridge); - - // First tick - os.tick(&mut vm, &signals, &mut hw); - let cycles_after_tick_1 = vm.cycles; - - // Should have stopped at FrameSync - assert!(cycles_after_tick_1 > 0, "VM should have consumed some cycles"); - assert!(cycles_after_tick_1 < VirtualMachineRuntime::CYCLES_PER_LOGICAL_FRAME); - - // Second tick - Should reset the budget and run a bit more until the next FrameSync - os.tick(&mut vm, &signals, &mut hw); - let cycles_after_tick_2 = vm.cycles; - - assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget"); - } - - #[test] - fn test_telemetry_cycles_budget() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - let signals = InputSignals::default(); - - // Standard budget - os.tick(&mut vm, &signals, &mut hw); - assert_eq!(os.telemetry_current.cycles_budget, VirtualMachineRuntime::CYCLES_PER_LOGICAL_FRAME); - - // Custom budget via CAP - let mut config = CertificationConfig::default(); - config.enabled = true; - config.cycles_budget_per_frame = Some(50000); - - let mut os_custom = VirtualMachineRuntime::new(Some(config)); - os_custom.tick(&mut vm, &signals, &mut hw); - assert_eq!(os_custom.telemetry_current.cycles_budget, 50000); - } - - #[test] - fn test_get_color_logic() { - let os = VirtualMachineRuntime::new(None); - - // Deve retornar a cor raw diretamente - assert_eq!(os.get_color(0x07E0), Color::GREEN); - assert_eq!(os.get_color(0xF800), Color::RED); - assert_eq!(os.get_color(0x001F), Color::BLUE); - assert_eq!(os.get_color(3), Color::from_raw(3)); - } - - #[test] - fn test_gfx_set_sprite_syscall_pops() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - - // Push arguments in order 1 to 10 - vm.push(Value::String("mouse_cursor".to_string())); // arg1: assetName - vm.push(Value::Int32(0)); // arg2: id - - // Simulating touch.x and touch.y syscalls - vm.push(Value::Int32(10)); // arg3: x (returned from syscall) - vm.push(Value::Int32(20)); // arg4: y (returned from syscall) - - vm.push(Value::Int32(0)); // arg5: tileId - vm.push(Value::Int32(0)); // arg6: paletteId - vm.push(Value::Boolean(true)); // arg7: active - vm.push(Value::Boolean(false)); // arg8: flipX - vm.push(Value::Boolean(false)); // arg9: flipY - vm.push(Value::Int32(4)); // arg10: priority - - let res = call_syscall(&mut os, 0x1007, &mut vm, &mut hw); - assert!(res.is_ok(), "GfxSetSprite syscall should succeed, but got: {:?}", res.err()); - } - - #[test] - fn test_gfx_set_sprite_with_swapped_arguments_repro() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - - // Repro: what if the compiler is pushing in reverse order? - vm.push(Value::Int32(4)); // arg10? - vm.push(Value::Boolean(false)); - vm.push(Value::Boolean(false)); - vm.push(Value::Boolean(true)); - vm.push(Value::Int32(0)); - vm.push(Value::Int32(0)); - vm.push(Value::Int32(20)); - vm.push(Value::Int32(10)); - vm.push(Value::Int32(0)); - vm.push(Value::String("mouse_cursor".to_string())); // arg1? - - let res = call_syscall(&mut os, 0x1007, &mut vm, &mut hw); - assert!(res.is_err()); - // Because it tries to pop priority but gets a string - match res.err().unwrap() { - VmFault::Trap(code, _) => assert_eq!(code, prometeu_bytecode::abi::TRAP_TYPE), - _ => panic!("Expected Trap"), - } - } - - #[test] - fn test_syscall_log_write_and_rate_limit() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - - os.current_app_id = 123; - - // 1. Normal log test - vm.push(Value::Int64(2)); // Info - vm.push(Value::String("Hello Log".to_string())); - let res = call_syscall(&mut os, 0x5001, &mut vm, &mut hw); - assert!(res.is_ok()); - - let recent = os.log_service.get_recent(1); - assert_eq!(recent[0].msg, "Hello Log"); - assert_eq!(recent[0].level, LogLevel::Info); - assert_eq!(recent[0].source, LogSource::App { app_id: 123 }); - - // 2. Truncation test - let long_msg = "A".repeat(300); - vm.push(Value::Int64(3)); // Warn - vm.push(Value::String(long_msg)); - call_syscall(&mut os, 0x5001, &mut vm, &mut hw).unwrap(); - - let recent = os.log_service.get_recent(1); - assert_eq!(recent[0].msg.len(), 256); - assert!(recent[0].msg.starts_with("AAAAA")); - - // 3. Rate Limit Test - // We already made 2 logs. The limit is 10. - for i in 0..8 { - vm.push(Value::Int64(2)); - vm.push(Value::String(format!("Log {}", i))); - call_syscall(&mut os, 0x5001, &mut vm, &mut hw).unwrap(); - } - - // The 11th log should be ignored (and generate a system warning) - vm.push(Value::Int64(2)); - vm.push(Value::String("Eleventh log".to_string())); - call_syscall(&mut os, 0x5001, &mut vm, &mut hw).unwrap(); - - let recent = os.log_service.get_recent(2); - // The last log should be the rate limit warning (came after the 10th log attempted) - assert_eq!(recent[1].msg, "App exceeded log limit per frame"); - assert_eq!(recent[1].level, LogLevel::Warn); - // The log "Eleventh log" should not be there - assert_ne!(recent[0].msg, "Eleventh log"); - - // 4. Rate limit reset test in the next frame - os.begin_logical_frame(&InputSignals::default(), &mut hw); - vm.push(Value::Int64(2)); - vm.push(Value::String("New frame log".to_string())); - call_syscall(&mut os, 0x5001, &mut vm, &mut hw).unwrap(); - - let recent = os.log_service.get_recent(1); - assert_eq!(recent[0].msg, "New frame log"); - - // 5. LOG_WRITE_TAG test - vm.push(Value::Int64(2)); // Info - vm.push(Value::Int64(42)); // Tag - vm.push(Value::String("Tagged Log".to_string())); - call_syscall(&mut os, 0x5002, &mut vm, &mut hw).unwrap(); - - let recent = os.log_service.get_recent(1); - assert_eq!(recent[0].msg, "Tagged Log"); - assert_eq!(recent[0].tag, 42); - // Syscall de log é void: não empurra valor na pilha - - // 6. GFX Syscall return test - vm.push(Value::Int64(1)); // color_idx - call_syscall(&mut os, 0x1001, &mut vm, &mut hw).unwrap(); // gfx.clear - assert_eq!(vm.pop().unwrap(), Value::Null); - } - - #[test] - fn test_entrypoint_called_every_frame() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - let signals = InputSignals::default(); - - // PushI32 0 (0x17), then Ret (0x51) - let rom = prometeu_bytecode::BytecodeModule { - version: 0, - const_pool: vec![], - functions: vec![prometeu_bytecode::FunctionMeta { - code_offset: 0, - code_len: 10, - param_slots: 0, - local_slots: 0, - return_slots: 0, - max_stack_slots: 0, - }], - code: vec![ - 0x17, 0x00, // PushI32 - 0x00, 0x00, 0x00, 0x00, // value 0 - 0x11, 0x00, // Pop - 0x51, 0x00 // Ret - ], - debug_info: None, - exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }], - }.serialize(); - let cartridge = Cartridge { - app_id: 1234, - title: "test".to_string(), - app_version: "1.0.0".to_string(), - app_mode: AppMode::Game, - entrypoint: "0".to_string(), - program: rom, - assets: vec![], - asset_table: vec![], - preload: vec![], - }; - os.initialize_vm(&mut vm, &cartridge); - - // First frame - os.tick(&mut vm, &signals, &mut hw); - assert_eq!(os.logical_frame_index, 1); - assert!(!os.logical_frame_active); - assert!(vm.call_stack.is_empty()); - - // Second frame - Should call entrypoint again - os.tick(&mut vm, &signals, &mut hw); - assert_eq!(os.logical_frame_index, 2); - assert!(!os.logical_frame_active); - assert!(vm.call_stack.is_empty()); - } - - #[test] - fn test_os_unknown_syscall_returns_trap() { - let mut os = VirtualMachineRuntime::new(None); - let mut vm = VirtualMachine::default(); - let mut hw = Hardware::new(); - let mut ret = HostReturn::new(&mut vm.operand_stack); - - let res = os.syscall(0xDEADBEEF, &[], &mut ret, &mut HostContext::new(Some(&mut hw))); - assert!(res.is_err()); - match res.err().unwrap() { - VmFault::Trap(code, _) => assert_eq!(code, prometeu_bytecode::abi::TRAP_INVALID_SYSCALL), - _ => panic!("Expected Trap"), - } - } - - #[test] - fn test_gfx_clear565_syscall() { - let mut hw = Hardware::new(); - let mut os = VirtualMachineRuntime::new(None); - let mut stack = Vec::new(); - - // Success case - let args = vec![Value::Bounded(0xF800)]; // Red - { - let mut ret = HostReturn::new(&mut stack); - os.syscall(Syscall::GfxClear565 as u32, &args, &mut ret, &mut HostContext::new(Some(&mut hw))).unwrap(); - } - assert_eq!(stack.len(), 0); // void return - - // OOB case - let args = vec![Value::Bounded(0x10000)]; - { - let mut ret = HostReturn::new(&mut stack); - let res = os.syscall(Syscall::GfxClear565 as u32, &args, &mut ret, &mut HostContext::new(Some(&mut hw))); - assert!(res.is_err()); - match res.err().unwrap() { - VmFault::Trap(trap, _) => assert_eq!(trap, prometeu_bytecode::abi::TRAP_OOB), - _ => panic!("Expected Trap OOB"), - } - } - } - - #[test] - fn test_input_snapshots_syscalls() { - let mut hw = Hardware::new(); - let mut os = VirtualMachineRuntime::new(None); - - // Pad snapshot - let mut stack = Vec::new(); - { - let mut ret = HostReturn::new(&mut stack); - os.syscall(Syscall::InputPadSnapshot as u32, &[], &mut ret, &mut HostContext::new(Some(&mut hw))).unwrap(); - } - assert_eq!(stack.len(), 48); - - // Touch snapshot - let mut stack = Vec::new(); - { - let mut ret = HostReturn::new(&mut stack); - os.syscall(Syscall::InputTouchSnapshot as u32, &[], &mut ret, &mut HostContext::new(Some(&mut hw))).unwrap(); - } - assert_eq!(stack.len(), 6); - } - - #[test] - fn test_os_syscall_without_hardware_fails_graciously() { - let mut os = VirtualMachineRuntime::new(None); - let mut stack = Vec::new(); - let mut ret = HostReturn::new(&mut stack); - - // GfxClear needs hardware - let res = os.syscall(Syscall::GfxClear as u32, &[Value::Int64(0)], &mut ret, &mut HostContext::new(None)); - assert_eq!(res, Err(VmFault::Unavailable)); - - // SystemHasCart DOES NOT need hardware - let res = os.syscall(Syscall::SystemHasCart as u32, &[], &mut ret, &mut HostContext::new(None)); - assert!(res.is_ok()); - } -} - impl NativeInterface for VirtualMachineRuntime { /// Dispatches a syscall from the VM to the native implementation. /// @@ -857,7 +436,7 @@ impl NativeInterface for VirtualMachineRuntime { /// Each syscall returns the number of virtual cycles it consumed. fn syscall(&mut self, id: SyscallId, args: &[Value], ret: &mut HostReturn, ctx: &mut HostContext) -> Result<(), VmFault> { self.telemetry_current.syscalls += 1; - let syscall = Syscall::from_u32(id).ok_or_else(|| VmFault::Trap(prometeu_bytecode::abi::TRAP_INVALID_SYSCALL, format!( + let syscall = Syscall::from_u32(id).ok_or_else(|| VmFault::Trap(TRAP_INVALID_SYSCALL, format!( "Unknown syscall: 0x{:08X}", id )))?; @@ -958,7 +537,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::GfxSetSprite => { let asset_name = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string asset_name".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), }; let index = expect_int(args, 1)? as usize; let x = expect_int(args, 2)? as i32; @@ -971,7 +550,7 @@ impl NativeInterface for VirtualMachineRuntime { let priority = expect_int(args, 9)? as u8; let bank_id = hw.assets().find_slot_by_name(&asset_name, BankType::TILES).unwrap_or(0); - + if index < 512 { *hw.gfx_mut().sprite_mut(index) = Sprite { tile: Tile { id: tile_id, flip_x: false, flip_y: false, palette_id }, @@ -992,7 +571,7 @@ impl NativeInterface for VirtualMachineRuntime { let y = expect_int(args, 1)? as i32; let msg = match args.get(2).ok_or_else(|| VmFault::Panic("Missing message".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string message".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; let color_val = expect_int(args, 3)?; let color = self.get_color(color_val); @@ -1004,7 +583,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::GfxClear565 => { let color_val = expect_int(args, 0)? as u32; if color_val > 0xFFFF { - return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_OOB, "Color value out of bounds (bounded)".into())); + return Err(VmFault::Trap(TRAP_OOB, "Color value out of bounds (bounded)".into())); } let color = Color::from_raw(color_val as u16); hw.gfx_mut().clear(color); @@ -1209,7 +788,7 @@ impl NativeInterface for VirtualMachineRuntime { Value::Int32(i) => *i as f64, Value::Int64(i) => *i as f64, Value::Bounded(b) => *b as f64, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected number for pitch".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected number for pitch".into())), }; hw.audio_mut().play(0, sample_id as u16, voice_id, volume, pan, pitch, 0, prometeu_hal::LoopMode::Off); @@ -1221,7 +800,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::AudioPlay => { let asset_name = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_name".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string asset_name".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_name".into())), }; let sample_id = expect_int(args, 1)? as u16; let voice_id = expect_int(args, 2)? as usize; @@ -1232,7 +811,7 @@ impl NativeInterface for VirtualMachineRuntime { Value::Int32(i) => *i as f64, Value::Int64(i) => *i as f64, Value::Bounded(b) => *b as f64, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected number for pitch".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected number for pitch".into())), }; let loop_mode = match expect_int(args, 6)? { 0 => prometeu_hal::LoopMode::Off, @@ -1252,7 +831,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::FsOpen => { let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string path".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())), }; if self.fs_state != FsState::Mounted { ret.push_int(-1); @@ -1282,7 +861,7 @@ impl NativeInterface for VirtualMachineRuntime { let handle = expect_int(args, 0)? as u32; let content = match args.get(1).ok_or_else(|| VmFault::Panic("Missing content".into()))? { Value::String(s) => s.as_bytes().to_vec(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string content".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string content".into())), }; let path = self.open_files.get(&handle).ok_or_else(|| VmFault::Panic("Invalid handle".into()))?; match self.fs.write_file(path, &content) { @@ -1302,7 +881,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::FsListDir => { let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string path".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())), }; match self.fs.list_dir(&path) { Ok(entries) => { @@ -1317,7 +896,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::FsExists => { let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string path".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())), }; ret.push_bool(self.fs.exists(&path)); Ok(()) @@ -1326,7 +905,7 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::FsDelete => { let path = match args.get(0).ok_or_else(|| VmFault::Panic("Missing path".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string path".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string path".into())), }; match self.fs.delete(&path) { Ok(_) => ret.push_bool(true), @@ -1342,7 +921,7 @@ impl NativeInterface for VirtualMachineRuntime { let level = expect_int(args, 0)?; let msg = match args.get(1).ok_or_else(|| VmFault::Panic("Missing message".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string message".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; self.syscall_log_write(level, 0, msg)?; // void @@ -1354,7 +933,7 @@ impl NativeInterface for VirtualMachineRuntime { let tag = expect_int(args, 1)? as u16; let msg = match args.get(2).ok_or_else(|| VmFault::Panic("Missing message".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string message".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string message".into())), }; self.syscall_log_write(level, tag, msg)?; // void @@ -1365,15 +944,15 @@ impl NativeInterface for VirtualMachineRuntime { Syscall::AssetLoad => { let asset_id = match args.get(0).ok_or_else(|| VmFault::Panic("Missing asset_id".into()))? { Value::String(s) => s.clone(), - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Expected string asset_id".into())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Expected string asset_id".into())), }; let asset_type_val = expect_int(args, 1)? as u32; let slot_index = expect_int(args, 2)? as usize; - + let asset_type = match asset_type_val { 0 => BankType::TILES, 1 => BankType::SOUNDS, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Invalid asset type".to_string())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), }; let slot = SlotRef { asset_type, index: slot_index }; @@ -1416,7 +995,7 @@ impl NativeInterface for VirtualMachineRuntime { let asset_type = match asset_type_val { 0 => BankType::TILES, 1 => BankType::SOUNDS, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Invalid asset type".to_string())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), }; let info = hw.assets().bank_info(asset_type); let json = serde_json::to_string(&info).unwrap_or_default(); @@ -1429,7 +1008,7 @@ impl NativeInterface for VirtualMachineRuntime { let asset_type = match asset_type_val { 0 => BankType::TILES, 1 => BankType::SOUNDS, - _ => return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_TYPE, "Invalid asset type".to_string())), + _ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())), }; let slot = SlotRef { asset_type, index: slot_index }; let info = hw.assets().slot_info(slot); diff --git a/crates/console/prometeu-vm/Cargo.toml b/crates/console/prometeu-vm/Cargo.toml index e660d749..ebc104f7 100644 --- a/crates/console/prometeu-vm/Cargo.toml +++ b/crates/console/prometeu-vm/Cargo.toml @@ -5,5 +5,5 @@ edition = "2024" license.workspace = true [dependencies] -prometeu-bytecode = { path = "../../compiler/prometeu-bytecode" } +prometeu-bytecode = { path = "../prometeu-bytecode" } prometeu-hal = { path = "../prometeu-hal" } diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index 6221140c..458a23c8 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -5,8 +5,6 @@ pub mod local_addressing; pub mod verifier; pub mod vm_init_error; -pub use prometeu_bytecode::abi::TrapInfo; -pub use prometeu_bytecode::opcode::OpCode; pub use prometeu_hal::{ HostContext, HostReturn, NativeInterface, SyscallId, }; diff --git a/crates/console/prometeu-vm/src/local_addressing.rs b/crates/console/prometeu-vm/src/local_addressing.rs index 87ab94d6..a703783b 100644 --- a/crates/console/prometeu-vm/src/local_addressing.rs +++ b/crates/console/prometeu-vm/src/local_addressing.rs @@ -1,5 +1,5 @@ use crate::call_frame::CallFrame; -use prometeu_bytecode::abi::{TrapInfo, TRAP_INVALID_LOCAL}; +use prometeu_bytecode::{TrapInfo, TRAP_INVALID_LOCAL}; use prometeu_bytecode::FunctionMeta; /// Computes the absolute stack index for the start of the current frame's locals (including args). diff --git a/crates/console/prometeu-vm/src/verifier.rs b/crates/console/prometeu-vm/src/verifier.rs index da1b018f..ac7bf037 100644 --- a/crates/console/prometeu-vm/src/verifier.rs +++ b/crates/console/prometeu-vm/src/verifier.rs @@ -1,9 +1,9 @@ use prometeu_hal::syscalls::Syscall; -use prometeu_bytecode::decoder::{decode_next, DecodeError}; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::opcode_spec::OpCodeSpecExt; +use prometeu_bytecode::{decode_next, DecodeError}; +use prometeu_bytecode::OpCode; +use prometeu_bytecode::OpCodeSpecExt; use prometeu_bytecode::FunctionMeta; -use prometeu_bytecode::layout; +use prometeu_bytecode::{compute_function_layouts, FunctionLayout}; use std::collections::{HashMap, HashSet, VecDeque}; #[derive(Debug, Clone, PartialEq, Eq)] @@ -28,7 +28,7 @@ impl Verifier { pub fn verify(code: &[u8], functions: &[FunctionMeta]) -> Result, VerifierError> { let mut max_stacks = Vec::with_capacity(functions.len()); // Precompute function [start, end) ranges once for O(1) lookups - let layouts = layout::compute_function_layouts(functions, code.len()); + let layouts = compute_function_layouts(functions, code.len()); for (i, func) in functions.iter().enumerate() { max_stacks.push(Self::verify_function(code, func, i, functions, &layouts)?); } @@ -40,7 +40,7 @@ impl Verifier { func: &FunctionMeta, func_idx: usize, all_functions: &[FunctionMeta], - layouts: &[layout::FunctionLayout], + layouts: &[FunctionLayout], ) -> Result { let func_start = func.code_offset as usize; // Use precomputed canonical range end diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index e2eb24b0..86495da9 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -1,16 +1,13 @@ use crate::call_frame::CallFrame; use crate::scope_frame::ScopeFrame; -use crate::{HostContext, NativeInterface}; -use prometeu_bytecode::abi::{ - TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_GATE, TRAP_OOB, - TRAP_TYPE, -}; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::program_image::ProgramImage; -use prometeu_bytecode::Value; -use prometeu_hal::vm_fault::VmFault; use crate::verifier::Verifier; use crate::vm_init_error::VmInitError; +use crate::{HostContext, NativeInterface}; +use prometeu_bytecode::{TrapInfo, TRAP_BAD_RET_SLOTS, TRAP_DEAD_GATE, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_GATE, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE}; +use prometeu_bytecode::OpCode; +use prometeu_bytecode::ProgramImage; +use prometeu_bytecode::Value; +use prometeu_hal::vm_fault::VmFault; /// 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 @@ -363,7 +360,7 @@ impl VirtualMachine { let start_pc = self.pc; // Fetch & Decode - let instr = prometeu_bytecode::decoder::decode_next(self.pc, &self.program.rom) + let instr = prometeu_bytecode::decode_next(self.pc, &self.program.rom) .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; let opcode = instr.opcode; @@ -884,7 +881,7 @@ impl VirtualMachine { let pc_at_syscall = start_pc as u32; let id = instr.imm_u32().map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?; let syscall = prometeu_hal::syscalls::Syscall::from_u32(id).ok_or_else(|| { - self.trap(prometeu_bytecode::abi::TRAP_INVALID_SYSCALL, OpCode::Syscall as u16, format!("Unknown syscall: 0x{:08X}", id), pc_at_syscall) + self.trap(TRAP_INVALID_SYSCALL, OpCode::Syscall as u16, format!("Unknown syscall: 0x{:08X}", id), pc_at_syscall) })?; let args_count = syscall.args_count(); @@ -892,7 +889,7 @@ impl VirtualMachine { let mut args = Vec::with_capacity(args_count); for _ in 0..args_count { let v = self.pop().map_err(|_e| { - self.trap(prometeu_bytecode::abi::TRAP_STACK_UNDERFLOW, OpCode::Syscall as u16, "Syscall argument stack underflow".to_string(), pc_at_syscall) + self.trap(TRAP_STACK_UNDERFLOW, OpCode::Syscall as u16, "Syscall argument stack underflow".to_string(), pc_at_syscall) })?; args.push(v); } @@ -1007,9 +1004,8 @@ mod tests { }]); vm } - use crate::{HostReturn}; - use prometeu_bytecode::abi::SourceSpan; - use prometeu_bytecode::FunctionMeta; + use crate::HostReturn; + use prometeu_bytecode::{FunctionMeta, TRAP_INVALID_LOCAL, TRAP_STACK_UNDERFLOW}; use prometeu_hal::expect_int; struct MockNative; @@ -1669,7 +1665,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_OOB); + assert_eq!(trap.code, TRAP_OOB); assert_eq!(trap.opcode, OpCode::GateLoad as u16); assert!(trap.message.contains("Out-of-bounds")); } @@ -1696,7 +1692,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_TYPE); + assert_eq!(trap.code, TRAP_TYPE); assert_eq!(trap.opcode, OpCode::GateLoad as u16); } _ => panic!("Expected Trap, got {:?}", report.reason), @@ -1772,7 +1768,7 @@ mod tests { let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_INVALID_GATE); + assert_eq!(trap.code, TRAP_INVALID_GATE); assert_eq!(trap.opcode, OpCode::GateLoad as u16); } _ => panic!("Expected Trap, got {:?}", report.reason), @@ -1903,7 +1899,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_TYPE); + assert_eq!(trap.code, TRAP_TYPE); assert_eq!(trap.opcode, OpCode::Syscall as u16); } _ => panic!("Expected Trap, got {:?}", report.reason), @@ -1925,7 +1921,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_INVALID_SYSCALL); + assert_eq!(trap.code, TRAP_INVALID_SYSCALL); assert_eq!(trap.opcode, OpCode::Syscall as u16); assert!(trap.message.contains("Unknown syscall")); assert_eq!(trap.pc, 0); @@ -1950,7 +1946,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_STACK_UNDERFLOW); + assert_eq!(trap.code, TRAP_STACK_UNDERFLOW); assert_eq!(trap.opcode, OpCode::Syscall as u16); assert!(trap.message.contains("underflow")); assert_eq!(trap.pc, 0); @@ -1998,7 +1994,7 @@ mod tests { assert!(res.is_err()); match res.err().unwrap() { VmFault::Trap(code, _) => { - assert_eq!(code, prometeu_bytecode::abi::TRAP_OOB); + assert_eq!(code, TRAP_OOB); } _ => panic!("Expected Trap"), } @@ -2430,7 +2426,7 @@ mod tests { match report.reason { LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, prometeu_bytecode::abi::TRAP_INVALID_LOCAL); + assert_eq!(trap.code, TRAP_INVALID_LOCAL); assert_eq!(trap.opcode, OpCode::GetLocal as u16); assert!(trap.message.contains("out of bounds")); } @@ -2577,97 +2573,4 @@ mod tests { _ => panic!("Expected Trap, got {:?}", report.reason), } } - - #[test] - fn test_traceable_trap_with_span() { - let mut rom = Vec::new(); - // 0: PUSH_I32 10 (6 bytes) - // 6: PUSH_I32 0 (6 bytes) - // 12: DIV (2 bytes) - rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - rom.extend_from_slice(&10i32.to_le_bytes()); - rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - rom.extend_from_slice(&0i32.to_le_bytes()); - rom.extend_from_slice(&(OpCode::Div as u16).to_le_bytes()); - - let mut pc_to_span = Vec::new(); - pc_to_span.push((0, SourceSpan { file_id: 1, start: 10, end: 15 })); - pc_to_span.push((6, SourceSpan { file_id: 1, start: 16, end: 20 })); - pc_to_span.push((12, SourceSpan { file_id: 1, start: 21, end: 25 })); - - let debug_info = prometeu_bytecode::DebugInfo { - pc_to_span, - function_names: vec![(0, "main".to_string())], - }; - - let program = ProgramImage::new(rom.clone(), vec![], vec![FunctionMeta { - code_offset: 0, - code_len: rom.len() as u32, - ..Default::default() - }], Some(debug_info), std::collections::HashMap::new()); - let mut vm = VirtualMachine { - program, - ..Default::default() - }; - - let mut native = MockNative; - let mut ctx = HostContext::new(None); - - vm.prepare_call("0"); - let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - match report.reason { - LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, TRAP_DIV_ZERO); - assert_eq!(trap.pc, 12); - assert_eq!(trap.span, Some(SourceSpan { file_id: 1, start: 21, end: 25 })); - } - _ => panic!("Expected Trap, got {:?}", report.reason), - } - } - - #[test] - fn test_traceable_trap_with_function_name() { - let mut rom = Vec::new(); - // 0: PUSH_I32 10 (6 bytes) - // 6: PUSH_I32 0 (6 bytes) - // 12: DIV (2 bytes) - rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - rom.extend_from_slice(&10i32.to_le_bytes()); - rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); - rom.extend_from_slice(&0i32.to_le_bytes()); - rom.extend_from_slice(&(OpCode::Div as u16).to_le_bytes()); - - let pc_to_span = vec![(12, SourceSpan { file_id: 1, start: 21, end: 25 })]; - let function_names = vec![(0, "math_utils::divide".to_string())]; - - let debug_info = prometeu_bytecode::DebugInfo { - pc_to_span, - function_names, - }; - - let functions = vec![FunctionMeta { - code_offset: 0, - code_len: rom.len() as u32, - ..Default::default() - }]; - - let program = ProgramImage::new(rom, vec![], functions, Some(debug_info), std::collections::HashMap::new()); - let mut vm = VirtualMachine { - program, - ..Default::default() - }; - - let mut native = MockNative; - let mut ctx = HostContext::new(None); - - vm.prepare_call("0"); - let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); - match report.reason { - LogicalFrameEndingReason::Trap(trap) => { - assert_eq!(trap.code, TRAP_DIV_ZERO); - assert!(trap.message.contains("math_utils::divide")); - } - _ => panic!("Expected Trap, got {:?}", report.reason), - } - } } diff --git a/crates/host/prometeu-host-desktop-winit/README.md b/crates/host/prometeu-host-desktop-winit/README.md deleted file mode 100644 index da183ccf..00000000 --- a/crates/host/prometeu-host-desktop-winit/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# PROMETEU Desktop Runtime - -This is the host implementation for desktop platforms, allowing PROMETEU to run on Windows, Linux, and macOS. - -## Features - -- **Rendering**: Uses the GPU via the `pixels` library to present the low-resolution framebuffer. -- **Input**: Translates keyboard and controller events (via `winit`) into PROMETEU hardware signals. -- **Audio**: Interfaces with the host's sound system (via `cpal`). -- **Debugging**: Hosts a TCP server that implements the **DevTools Protocol**, allowing connections from external IDEs or debuggers. - -## How to run - -Generally executed via the main CLI (`prometeu`), but can be called directly for testing: - -```bash -cargo run -- --run path/to/cart -``` diff --git a/crates/tools/prometeu-cli/README.md b/crates/tools/prometeu-cli/README.md deleted file mode 100644 index 0d29baa4..00000000 --- a/crates/tools/prometeu-cli/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# PROMETEU CLI (Dispatcher) - -The `prometeu` binary acts as the unified front-end for the ecosystem. It does not implement the execution or compilation logic but knows where to find the binaries that do. - -## Commands - -- `prometeu run `: Runs a cartridge using the available runtime. -- `prometeu debug [--port

]`: Starts execution in debug mode. -- `prometeu build `: Calls the `prometeuc` compiler. -- `prometeu verify c `: Calls the `prometeuc` compiler. -- `prometeu pack `: (Planned) Calls the `prometeup` packager. -- `prometeu verify p `: (Planned) Calls the `prometeup` packager. - -## How it works - -The dispatcher locates sibling binaries (`prometeu-host-desktop-winit`, `prometeuc`, etc.) in the same directory where it is installed. It inherits `stdin`, `stdout`, and `stderr`, and propagates the exit code of the called process. diff --git a/crates/tools/prometeu-packer/.gitkeep b/crates/tools/prometeu-packer/.gitkeep deleted file mode 100644 index e69de29b..00000000