From a7b824fe21163ee8230fa513246a40f03fd7a5d2 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Fri, 23 Jan 2026 06:52:31 +0000 Subject: [PATCH] clean up, debugger, add comments --- crates/prometeu-bytecode/README.md | 142 ++++++------ crates/prometeu-bytecode/src/abi.rs | 9 + crates/prometeu-bytecode/src/asm.rs | 18 ++ crates/prometeu-bytecode/src/disasm.rs | 10 + crates/prometeu-bytecode/src/lib.rs | 61 ++--- crates/prometeu-bytecode/src/opcode.rs | 131 ++++++++++- crates/prometeu-bytecode/src/pbc.rs | 73 ++++++ crates/prometeu-bytecode/src/readwrite.rs | 11 + crates/prometeu-compiler/src/cli.rs | 40 ---- .../prometeu-compiler/src/codegen/ast_util.rs | 14 +- .../prometeu-compiler/src/codegen/codegen.rs | 208 ++++++++++++------ crates/prometeu-compiler/src/codegen/mod.rs | 15 +- .../src/codegen/syscall_map.rs | 20 ++ .../src/codegen/validator.rs | 36 ++- crates/prometeu-compiler/src/compiler.rs | 65 ++++-- crates/prometeu-compiler/src/lib.rs | 71 +++++- crates/prometeu-compiler/src/main.rs | 2 + crates/prometeu-compiler/src/syscall_map.rs | 27 --- crates/prometeu-core/src/debugger_protocol.rs | 27 ++- crates/prometeu-core/src/firmware/firmware.rs | 28 ++- .../src/firmware/firmware_state.rs | 10 +- .../firmware/firmware_step_crash_screen.rs | 2 +- .../src/firmware/firmware_step_launch_hub.rs | 2 +- .../firmware/firmware_step_load_cartridge.rs | 2 +- crates/prometeu-core/src/firmware/mod.rs | 2 +- crates/prometeu-core/src/fs/mod.rs | 4 +- crates/prometeu-core/src/fs/virtual_fs.rs | 9 + crates/prometeu-core/src/hardware/asset.rs | 6 +- crates/prometeu-core/src/hardware/audio.rs | 20 +- crates/prometeu-core/src/hardware/gfx.rs | 64 +++--- crates/prometeu-core/src/hardware/hardware.rs | 29 ++- .../src/hardware/memory_banks.rs | 2 +- crates/prometeu-core/src/hardware/mod.rs | 2 + crates/prometeu-core/src/hardware/pad.rs | 2 +- .../src/{prometeu_os => hardware}/syscalls.rs | 65 +++++- crates/prometeu-core/src/hardware/touch.rs | 2 +- crates/prometeu-core/src/lib.rs | 17 ++ crates/prometeu-core/src/log/log_service.rs | 2 +- crates/prometeu-core/src/log/mod.rs | 4 +- .../src/model/cartridge_loader.rs | 2 +- crates/prometeu-core/src/model/color.rs | 15 +- crates/prometeu-core/src/model/mod.rs | 2 +- crates/prometeu-core/src/model/tile_layer.rs | 2 +- crates/prometeu-core/src/prometeu_hub/mod.rs | 1 + .../src/prometeu_hub/prometeu_hub.rs | 88 +------- crates/prometeu-core/src/prometeu_os/mod.rs | 4 +- .../src/prometeu_os/prometeu_os.rs | 83 ++++--- crates/prometeu-core/src/telemetry.rs | 1 + .../prometeu-core/src/virtual_machine/mod.rs | 14 +- .../src/virtual_machine/native_interface.rs | 13 -- .../src/virtual_machine/program.rs | 2 +- .../src/virtual_machine/value.rs | 15 +- .../src/virtual_machine/virtual_machine.rs | 43 ++-- crates/prometeu-runtime-desktop/src/cap.rs | 2 +- .../prometeu-runtime-desktop/src/debugger.rs | 9 +- .../src/fs_backend.rs | 6 +- crates/prometeu-runtime-desktop/src/input.rs | 4 +- crates/prometeu-runtime-desktop/src/lib.rs | 6 +- crates/prometeu-runtime-desktop/src/runner.rs | 22 +- devtools/debugger-protocol/protocol.json | 2 +- 60 files changed, 1033 insertions(+), 557 deletions(-) delete mode 100644 crates/prometeu-compiler/src/cli.rs create mode 100644 crates/prometeu-compiler/src/codegen/syscall_map.rs delete mode 100644 crates/prometeu-compiler/src/syscall_map.rs rename crates/prometeu-core/src/{prometeu_os => hardware}/syscalls.rs (70%) delete mode 100644 crates/prometeu-core/src/virtual_machine/native_interface.rs diff --git a/crates/prometeu-bytecode/README.md b/crates/prometeu-bytecode/README.md index cc96b1b1..225757d4 100644 --- a/crates/prometeu-bytecode/README.md +++ b/crates/prometeu-bytecode/README.md @@ -1,116 +1,116 @@ # prometeu-bytecode -Contrato oficial (ABI) do ecossistema PROMETEU. Este crate define o conjunto de instruções (ISA), o formato de arquivo `.pbc` (Prometeu ByteCode) e ferramentas básicas de montagem (Assembler) e desmontagem (Disassembler). +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 -A PVM (Prometeu Virtual Machine) é uma máquina baseada em pilha (**stack-based**). A maioria das instruções opera nos valores do topo da pilha de operandos. O formato de dados padrão para multi-byte na ROM é **Little-Endian**. +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**. -### Convenção de Notação da Pilha -Nas tabelas abaixo, usamos a seguinte notação para representar o estado da pilha: +### Stack Notation Convention +In the tables below, we use the following notation to represent the state of the stack: `[a, b] -> [c]` -Significa que a instrução remove `a` e `b` da pilha (onde `b` estava no topo) e insere `c` no topo. +Means the instruction removes `a` and `b` from the stack (where `b` was at the top) and pushes `c` to the top. --- -## Conjunto de Instruções (ISA) +## Instruction Set Architecture (ISA) -### 6.1 Controle de Execução +### 6.1 Execution Control -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `Nop` | `0x00` | - | `[] -> []` | Nenhuma operação. | -| `Halt` | `0x01` | - | `[] -> []` | Interrompe a execução da VM permanentemente. | -| `Jmp` | `0x02` | `addr: u32` | `[] -> []` | Salto incondicional para o endereço absoluto `addr`. | -| `JmpIfFalse`| `0x03` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `false`. | -| `JmpIfTrue` | `0x04` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `true`. | -| `Trap` | `0x05` | - | `[] -> []` | Interrupção para debugger (breakpoint). | +| `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 Manipulação da Pilha +### 6.2 Stack Manipulation -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `PushConst` | `0x10` | `idx: u32` | `[] -> [val]` | Carrega a constante do índice `idx` da Constant Pool. | -| `Pop` | `0x11` | - | `[val] -> []` | Remove e descarta o valor do topo da pilha. | -| `Dup` | `0x12` | - | `[val] -> [val, val]` | Duplica o valor no topo da pilha. | -| `Swap` | `0x13` | - | `[a, b] -> [b, a]` | Inverte a posição dos dois valores no topo. | -| `PushI64` | `0x14` | `val: i64` | `[] -> [i64]` | Empilha um inteiro de 64 bits imediato. | -| `PushF64` | `0x15` | `val: f64` | `[] -> [f64]` | Empilha um ponto flutuante de 64 bits imediato. | -| `PushBool` | `0x16` | `val: u8` | `[] -> [bool]` | Empilha um booleano (0=false, 1=true). | -| `PushI32` | `0x17` | `val: i32` | `[] -> [i32]` | Empilha um inteiro de 32 bits imediato. | -| `PopN` | `0x18` | `n: u16` | `[...] -> [...]` | Remove `n` valores da pilha de uma vez. | +| `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 Aritmética -A VM realiza promoção automática de tipos (ex: `i32` + `f64` resulta em `f64`). +### 6.3 Arithmetic +The VM performs automatic type promotion (e.g., `i32` + `f64` results in `f64`). -| OpCode | Valor | Pilha | Descrição | +| OpCode | Value | Stack | Description | | :--- | :--- | :--- | :--- | -| `Add` | `0x20` | `[a, b] -> [a + b]` | Soma os dois valores do topo. | -| `Sub` | `0x21` | `[a, b] -> [a - b]` | Subtrai `b` de `a`. | -| `Mul` | `0x22` | `[a, b] -> [a * b]` | Multiplica os dois valores do topo. | -| `Div` | `0x23` | `[a, b] -> [a / b]` | Divide `a` por `b`. Erro se `b == 0`. | -| `Neg` | `0x3E` | `[a] -> [-a]` | Inverte o sinal numérico. | +| `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 Lógica e Comparação +### 6.4 Logic and Comparison -| OpCode | Valor | Pilha | Descrição | +| OpCode | Value | Stack | Description | | :--- | :--- | :--- | :--- | -| `Eq` | `0x30` | `[a, b] -> [bool]` | Testa igualdade. | -| `Neq` | `0x31` | `[a, b] -> [bool]` | Testa desigualdade. | +| `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]` | AND lógico (booleano). | -| `Or` | `0x35` | `[a, b] -> [bool]` | OR lógico (booleano). | -| `Not` | `0x36` | `[a] -> [!a]` | NOT lógico. | -| `BitAnd` | `0x37` | `[a, b] -> [int]` | AND bit a bit. | -| `BitOr` | `0x38` | `[a, b] -> [int]` | OR bit a bit. | -| `BitXor` | `0x39` | `[a, b] -> [int]` | XOR bit a bit. | +| `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 Variáveis e Memória +### 6.5 Variables and Memory -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `GetGlobal`| `0x40` | `idx: u32` | `[] -> [val]` | Carrega valor da global `idx`. | -| `SetGlobal`| `0x41` | `idx: u32` | `[val] -> []` | Armazena topo na global `idx`. | -| `GetLocal` | `0x42` | `idx: u32` | `[] -> [val]` | Carrega local `idx` do frame atual. | -| `SetLocal` | `0x43` | `idx: u32` | `[val] -> []` | Armazena topo na local `idx` do frame atual. | +| `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 Funções e Escopo +### 6.6 Functions and Scope -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `Call` | `0x50` | `addr: u32, args: u32` | `[a1, a2] -> [...]` | Chama `addr`. Os `args` valores no topo viram locais do novo frame. | -| `Ret` | `0x51` | - | `[val] -> [val]` | Retorna da função atual, limpando o frame e devolvendo o valor do topo. | -| `PushScope`| `0x52` | - | `[] -> []` | Inicia um sub-escopo (bloco) para locais temporários. | -| `PopScope` | `0x53` | - | `[] -> []` | Finaliza sub-escopo, removendo locais criados nele da pilha. | +| `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 (Memória Dinâmica) +### 6.7 Heap (Dynamic Memory) -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `Alloc` | `0x60` | `size: u32` | `[] -> [ref]` | Aloca `size` slots no heap e retorna uma referência. | -| `LoadRef` | `0x61` | `offset: u32`| `[ref] -> [val]` | Lê valor do heap no endereço `ref + offset`. | -| `StoreRef`| `0x62` | `offset: u32`| `[ref, val] -> []` | Escreve `val` no heap no endereço `ref + offset`. | +| `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 Sistema e Sincronização +### 6.8 System and Synchronization -| OpCode | Valor | Operandos | Pilha | Descrição | +| OpCode | Value | Operands | Stack | Description | | :--- | :--- | :--- | :--- | :--- | -| `Syscall` | `0x70` | `id: u32` | `[...] -> [...]` | Invoca uma função do sistema/firmware. A pilha depende da syscall. | -| `FrameSync`| `0x80` | - | `[] -> []` | Marca o fim do processamento do frame lógico atual (60 FPS). | +| `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). | --- -## Estrutura do PBC (Prometeu ByteCode) +## PBC Structure (Prometeu ByteCode) -O PBC é o formato binário oficial para programas Prometeu. +PBC is the official binary format for Prometeu programs. ```rust -// Exemplo de como carregar um arquivo PBC +// Example of how to load a PBC file let bytes = std::fs::read("game.pbc")?; let pbc = prometeu_bytecode::pbc::parse_pbc(&bytes)?; @@ -120,11 +120,11 @@ println!("Constants: {}", pbc.cp.len()); --- -## Assembler e Disassembler +## Assembler and Disassembler -Este crate fornece ferramentas para facilitar a geração e inspeção de código. +This crate provides tools to facilitate code generation and inspection. -### Montagem (Assembler) +### Assembly (Assembler) ```rust use prometeu_bytecode::asm::{assemble, Asm, Operand}; use prometeu_bytecode::opcode::OpCode; @@ -139,7 +139,7 @@ let instructions = vec![ let rom_bytes = assemble(&instructions).unwrap(); ``` -### Desmontagem (Disassembler) +### Disassembly (Disassembler) ```rust use prometeu_bytecode::disasm::disasm; diff --git a/crates/prometeu-bytecode/src/abi.rs b/crates/prometeu-bytecode/src/abi.rs index 3cde16d2..777bcf53 100644 --- a/crates/prometeu-bytecode/src/abi.rs +++ b/crates/prometeu-bytecode/src/abi.rs @@ -1,5 +1,12 @@ +//! 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; +/// 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 { match opcode { OpCode::PushConst => 4, @@ -17,6 +24,7 @@ pub fn operand_size(opcode: OpCode) -> usize { } } +/// Checks if an instruction is a jump (branch) instruction. pub fn is_jump(opcode: OpCode) -> bool { match opcode { OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => true, @@ -24,6 +32,7 @@ pub fn is_jump(opcode: OpCode) -> bool { } } +/// Checks if an instruction has any immediate operands in the instruction stream. pub fn has_immediate(opcode: OpCode) -> bool { operand_size(opcode) > 0 } diff --git a/crates/prometeu-bytecode/src/asm.rs b/crates/prometeu-bytecode/src/asm.rs index 0f7d2b45..f0ee1337 100644 --- a/crates/prometeu-bytecode/src/asm.rs +++ b/crates/prometeu-bytecode/src/asm.rs @@ -2,22 +2,33 @@ 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), } +/// 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 { @@ -30,6 +41,13 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec) -> u32 { pcp } +/// 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 mut labels = HashMap::new(); let mut current_pc = 0u32; diff --git a/crates/prometeu-bytecode/src/disasm.rs b/crates/prometeu-bytecode/src/disasm.rs index c5e8376c..57210343 100644 --- a/crates/prometeu-bytecode/src/disasm.rs +++ b/crates/prometeu-bytecode/src/disasm.rs @@ -2,13 +2,18 @@ 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), @@ -18,6 +23,11 @@ pub enum DisasmOperand { 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); diff --git a/crates/prometeu-bytecode/src/lib.rs b/crates/prometeu-bytecode/src/lib.rs index 7050a9d6..1ee812d4 100644 --- a/crates/prometeu-bytecode/src/lib.rs +++ b/crates/prometeu-bytecode/src/lib.rs @@ -1,51 +1,22 @@ +//! # Prometeu Bytecode (PBC) +//! +//! This crate defines the core Application Binary Interface (ABI) and Instruction Set Architecture (ISA) +//! for the Prometeu Virtual Machine (PVM). +//! +//! It serves as the "source of truth" for how programs are structured, encoded, and executed +//! within the ecosystem. +//! +//! ## Core Components: +//! - [`opcode`]: Defines the available instructions and their performance characteristics. +//! - [`pbc`]: Handles the serialization and deserialization of `.pbc` files. +//! - [`abi`]: Specifies the binary rules for operands and stack behavior. +//! - [`asm`]: Provides a programmatic Assembler to convert high-level instructions to bytes. +//! - [`disasm`]: Provides a Disassembler to inspect compiled bytecode. +//! - [`readwrite`]: Internal utilities for Little-Endian binary I/O. + pub mod opcode; pub mod abi; pub mod pbc; pub mod readwrite; pub mod asm; pub mod disasm; - -#[cfg(test)] -mod tests { - use crate::opcode::OpCode; - use crate::asm::{self, Asm, Operand}; - use crate::pbc::{self, PbcFile, ConstantPoolEntry}; - use crate::disasm; - - #[test] - fn test_golden_abi_roundtrip() { - // 1. Create a simple assembly program: PushI32 42; Halt - let instructions = vec![ - Asm::Op(OpCode::PushI32, vec![Operand::I32(42)]), - Asm::Op(OpCode::Halt, vec![]), - ]; - - let rom = asm::assemble(&instructions).expect("Failed to assemble"); - - // 2. Create a PBC file - let pbc_file = PbcFile { - cp: vec![ConstantPoolEntry::Int32(100)], // Random CP entry - rom, - }; - - let bytes = pbc::write_pbc(&pbc_file).expect("Failed to write PBC"); - - // 3. Parse it back - let parsed_pbc = pbc::parse_pbc(&bytes).expect("Failed to parse PBC"); - - assert_eq!(parsed_pbc.cp, pbc_file.cp); - assert_eq!(parsed_pbc.rom, pbc_file.rom); - - // 4. Disassemble ROM - let dis_instrs = disasm::disasm(&parsed_pbc.rom).expect("Failed to disassemble"); - - assert_eq!(dis_instrs.len(), 2); - assert_eq!(dis_instrs[0].opcode, OpCode::PushI32); - if let disasm::DisasmOperand::U32(v) = dis_instrs[0].operands[0] { - assert_eq!(v, 42); - } else { - panic!("Wrong operand type"); - } - assert_eq!(dis_instrs[1].opcode, OpCode::Halt); - } -} diff --git a/crates/prometeu-bytecode/src/opcode.rs b/crates/prometeu-bytecode/src/opcode.rs index c0e54dc4..be711438 100644 --- a/crates/prometeu-bytecode/src/opcode.rs +++ b/crates/prometeu-bytecode/src/opcode.rs @@ -1,67 +1,180 @@ +/// Represents a single instruction in the Prometeu Virtual Machine. +/// +/// Each OpCode is encoded as a 16-bit unsigned integer (u16) in the bytecode. +/// The PVM is a stack-based machine, meaning most instructions take their +/// operands from the top of the stack and push their results back onto it. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u16)] pub enum OpCode { - // 6.1 Execution Control + // --- 6.1 Execution Control --- + + /// No operation. Does nothing for 1 cycle. Nop = 0x00, + /// Stops the Virtual Machine execution immediately. Halt = 0x01, + /// Unconditional jump to a specific PC (Program Counter) address. + /// Operand: addr (u32) Jmp = 0x02, + /// Jumps to `addr` if the value at the top of the stack is `false`. + /// Operand: addr (u32) + /// Stack: [bool] -> [] JmpIfFalse = 0x03, + /// Jumps to `addr` if the value at the top of the stack is `true`. + /// Operand: addr (u32) + /// Stack: [bool] -> [] JmpIfTrue = 0x04, + /// Triggers a software breakpoint. Used for debugging. Trap = 0x05, - // 6.2 Stack + // --- 6.2 Stack Manipulation --- + + /// Loads a constant from the Constant Pool into the stack. + /// Operand: index (u32) + /// Stack: [] -> [value] PushConst = 0x10, + /// Removes the top value from the stack. + /// Stack: [val] -> [] Pop = 0x11, + /// Duplicates the top value of the stack. + /// Stack: [val] -> [val, val] Dup = 0x12, + /// Swaps the two top values of the stack. + /// Stack: [a, b] -> [b, a] Swap = 0x13, + /// Pushes a 64-bit integer literal onto the stack. + /// Operand: value (i64) PushI64 = 0x14, + /// Pushes a 64-bit float literal onto the stack. + /// Operand: value (f64) PushF64 = 0x15, + /// Pushes a boolean literal onto the stack (0=false, 1=true). + /// Operand: value (u8) PushBool = 0x16, + /// Pushes a 32-bit integer literal onto the stack. + /// Operand: value (i32) PushI32 = 0x17, + /// Removes `n` values from the stack. + /// Operand: n (u16) PopN = 0x18, - // 6.3 Arithmetic + // --- 6.3 Arithmetic --- + + /// Adds the two top values (a + b). + /// Stack: [a, b] -> [result] Add = 0x20, + /// Subtracts the top value from the second one (a - b). + /// Stack: [a, b] -> [result] Sub = 0x21, + /// Multiplies the two top values (a * b). + /// Stack: [a, b] -> [result] Mul = 0x22, + /// Divides the second top value by the top one (a / b). + /// Stack: [a, b] -> [result] Div = 0x23, - // 6.4 Comparison and Logic + // --- 6.4 Comparison and Logic --- + + /// Checks if a equals b. + /// Stack: [a, b] -> [bool] Eq = 0x30, + /// Checks if a is not equal to b. + /// Stack: [a, b] -> [bool] Neq = 0x31, + /// Checks if a is less than b. + /// Stack: [a, b] -> [bool] Lt = 0x32, + /// Checks if a is greater than b. + /// Stack: [a, b] -> [bool] Gt = 0x33, + /// Logical AND. + /// Stack: [bool, bool] -> [bool] And = 0x34, + /// Logical OR. + /// Stack: [bool, bool] -> [bool] Or = 0x35, + /// Logical NOT. + /// Stack: [bool] -> [bool] Not = 0x36, + /// Bitwise AND. + /// Stack: [int, int] -> [int] BitAnd = 0x37, + /// Bitwise OR. + /// Stack: [int, int] -> [int] BitOr = 0x38, + /// Bitwise XOR. + /// Stack: [int, int] -> [int] BitXor = 0x39, + /// Bitwise Shift Left. + /// Stack: [int, count] -> [int] Shl = 0x3A, + /// Bitwise Shift Right. + /// Stack: [int, count] -> [int] Shr = 0x3B, + /// Checks if a is less than or equal to b. + /// Stack: [a, b] -> [bool] Lte = 0x3C, + /// Checks if a is greater than or equal to b. + /// Stack: [a, b] -> [bool] Gte = 0x3D, + /// Negates a number (-a). + /// Stack: [num] -> [num] Neg = 0x3E, - // 6.5 Variables + // --- 6.5 Variables --- + + /// Loads a value from a global variable slot. + /// Operand: slot_index (u32) + /// Stack: [] -> [value] GetGlobal = 0x40, + /// Stores the top value into a global variable slot. + /// Operand: slot_index (u32) + /// Stack: [value] -> [] SetGlobal = 0x41, + /// Loads a value from a local variable slot in the current frame. + /// Operand: slot_index (u32) + /// Stack: [] -> [value] GetLocal = 0x42, + /// Stores the top value into a local variable slot in the current frame. + /// Operand: slot_index (u32) + /// Stack: [value] -> [] SetLocal = 0x43, - // 6.6 Functions + // --- 6.6 Functions --- + + /// Calls a function at a specific address. + /// Operands: addr (u32), args_count (u32) + /// Stack: [arg0, arg1, ...] -> [return_value] Call = 0x50, + /// Returns from the current function. + /// Stack: [return_val] -> [return_val] Ret = 0x51, + /// Starts a new local scope (for blocks/loops). PushScope = 0x52, + /// Ends the current local scope, discarding its local variables. PopScope = 0x53, - // 6.7 Heap + // --- 6.7 Heap --- + + /// Allocates `size` slots on the heap. + /// Stack: [size] -> [reference] Alloc = 0x60, + /// Reads a value from the heap at `reference + offset`. + /// Operand: offset (u32) + /// Stack: [reference] -> [value] LoadRef = 0x61, + /// Writes a value to the heap at `reference + offset`. + /// Operand: offset (u32) + /// Stack: [reference, value] -> [] StoreRef = 0x62, - // 6.8 Peripherals and System + // --- 6.8 Peripherals and System --- + + /// Invokes a system function (Firmware/OS). + /// Operand: syscall_id (u32) + /// Stack: [args...] -> [results...] (depends on syscall) Syscall = 0x70, + /// Synchronizes the VM with the hardware frame (usually 60Hz). + /// Execution pauses until the next VSync. FrameSync = 0x80, } @@ -123,6 +236,8 @@ impl TryFrom for OpCode { } impl OpCode { + /// Returns the cost of the instruction in VM cycles. + /// This is used for performance monitoring and resource limiting (Certification). pub fn cycles(&self) -> u64 { match self { OpCode::Nop => 1, diff --git a/crates/prometeu-bytecode/src/pbc.rs b/crates/prometeu-bytecode/src/pbc.rs index b69ca398..beee674e 100644 --- a/crates/prometeu-bytecode/src/pbc.rs +++ b/crates/prometeu-bytecode/src/pbc.rs @@ -1,22 +1,47 @@ use crate::readwrite::*; use std::io::{Cursor, Read, Write}; +/// An entry in the Constant Pool. +/// +/// The Constant Pool is a table of unique values used by the program. +/// Instead of embedding large data (like strings) directly in the instruction stream, +/// the bytecode uses `PushConst ` to load these values onto the stack. #[derive(Debug, Clone, PartialEq)] pub enum ConstantPoolEntry { + /// Reserved index (0). Represents a null/undefined value. Null, + /// A 64-bit integer constant. Int64(i64), + /// A 64-bit floating point constant. Float64(f64), + /// A boolean constant. Boolean(bool), + /// A UTF-8 string constant. String(String), + /// A 32-bit integer constant. Int32(i32), } +/// Represents a compiled Prometeu ByteCode (.pbc) file. +/// +/// The file format follows this structure (Little-Endian): +/// 1. Magic Header: "PPBC" (4 bytes) +/// 2. CP Count: u32 +/// 3. CP Entries: [Tag (u8), Data...] +/// 4. ROM Size: u32 +/// 5. ROM Data: [u16 OpCode, Operands...][] #[derive(Debug, Clone, Default)] pub struct PbcFile { + /// The list of constants used by the program. pub cp: Vec, + /// The raw instruction bytes (ROM). pub rom: Vec, } +/// Parses a raw byte buffer into a `PbcFile` structure. +/// +/// This function validates the "PPBC" signature and reconstructs the +/// Constant Pool and ROM data from the binary format. pub fn parse_pbc(bytes: &[u8]) -> Result { if bytes.len() < 4 || &bytes[0..4] != b"PPBC" { return Err("Invalid PBC signature".into()); @@ -68,6 +93,9 @@ pub fn parse_pbc(bytes: &[u8]) -> Result { Ok(PbcFile { cp, rom }) } +/// Serializes a `PbcFile` structure into a binary buffer. +/// +/// This is used by the compiler to generate the final .pbc file. pub fn write_pbc(pbc: &PbcFile) -> Result, String> { let mut out = Vec::new(); out.write_all(b"PPBC").map_err(|e| e.to_string())?; @@ -109,3 +137,48 @@ pub fn write_pbc(pbc: &PbcFile) -> Result, String> { Ok(out) } + +#[cfg(test)] +mod tests { + use crate::asm::{self, Asm, Operand}; + use crate::disasm; + use crate::opcode::OpCode; + use crate::pbc::{self, ConstantPoolEntry, PbcFile}; + + #[test] + fn test_golden_abi_roundtrip() { + // 1. Create a simple assembly program: PushI32 42; Halt + let instructions = vec![ + Asm::Op(OpCode::PushI32, vec![Operand::I32(42)]), + Asm::Op(OpCode::Halt, vec![]), + ]; + + let rom = asm::assemble(&instructions).expect("Failed to assemble"); + + // 2. Create a PBC file + let pbc_file = PbcFile { + cp: vec![ConstantPoolEntry::Int32(100)], // Random CP entry + rom, + }; + + let bytes = pbc::write_pbc(&pbc_file).expect("Failed to write PBC"); + + // 3. Parse it back + let parsed_pbc = pbc::parse_pbc(&bytes).expect("Failed to parse PBC"); + + assert_eq!(parsed_pbc.cp, pbc_file.cp); + assert_eq!(parsed_pbc.rom, pbc_file.rom); + + // 4. Disassemble ROM + let dis_instrs = disasm::disasm(&parsed_pbc.rom).expect("Failed to disassemble"); + + assert_eq!(dis_instrs.len(), 2); + assert_eq!(dis_instrs[0].opcode, OpCode::PushI32); + if let disasm::DisasmOperand::U32(v) = dis_instrs[0].operands[0] { + assert_eq!(v, 42); + } else { + panic!("Wrong operand type"); + } + assert_eq!(dis_instrs[1].opcode, OpCode::Halt); + } +} diff --git a/crates/prometeu-bytecode/src/readwrite.rs b/crates/prometeu-bytecode/src/readwrite.rs index c52bc1a6..6dc198da 100644 --- a/crates/prometeu-bytecode/src/readwrite.rs +++ b/crates/prometeu-bytecode/src/readwrite.rs @@ -1,41 +1,52 @@ +//! 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/prometeu-compiler/src/cli.rs b/crates/prometeu-compiler/src/cli.rs deleted file mode 100644 index 68c6f2fb..00000000 --- a/crates/prometeu-compiler/src/cli.rs +++ /dev/null @@ -1,40 +0,0 @@ -use clap::{Parser, Subcommand}; -use std::path::PathBuf; - -#[derive(Parser)] -#[command(name = "prometeu-compiler")] -#[command(about = "Prometeu Compiler", long_about = None)] -pub struct Cli { - #[command(subcommand)] - pub command: Commands, -} - -#[derive(Subcommand)] -pub enum Commands { - /// Builds a Prometeu project - Build { - /// Project directory - project_dir: PathBuf, - - /// Entry file - #[arg(short, long)] - entry: Option, - - /// Output PBC file - #[arg(short, long)] - out: Option, - - /// Emit disassembly file - #[arg(long, default_value_t = true)] - emit_disasm: bool, - - /// Emit symbols file - #[arg(long, default_value_t = true)] - emit_symbols: bool, - }, - /// Verifies a Prometeu project - Verify { - /// Project directory - project_dir: PathBuf, - }, -} diff --git a/crates/prometeu-compiler/src/codegen/ast_util.rs b/crates/prometeu-compiler/src/codegen/ast_util.rs index c2c13262..0d6ba5f0 100644 --- a/crates/prometeu-compiler/src/codegen/ast_util.rs +++ b/crates/prometeu-compiler/src/codegen/ast_util.rs @@ -1,15 +1,23 @@ +use anyhow::{anyhow, Result}; use oxc_ast::ast::*; -use anyhow::{Result, anyhow}; +/// Extracts the name of the function being called from a CallExpression. pub fn get_callee_name(expr: &Expression) -> Result { get_member_expr_name(expr) } +/// Recursively extracts a full name from an identifier or a member expression. +/// +/// Example: +/// - `foo` -> "foo" +/// - `Color.RED` -> "Color.RED" +/// - `PGfx.drawRect` -> "Gfx.drawRect" (Stripping 'P' prefix if appropriate) pub fn get_member_expr_name(expr: &Expression) -> Result { match expr { Expression::Identifier(ident) => { let name = ident.name.to_string(); - // Remove prefix 'P' if it's followed by an uppercase letter (e.g., PGfx -> Gfx) + // Prometeu SDK uses 'P' prefix for some internal names to avoid conflicts + // with standard JS identifiers. We strip it here to match the ISA names. if name.len() > 1 && name.starts_with('P') && name.chars().nth(1).unwrap().is_uppercase() { Ok(name[1..].to_string()) } else { @@ -21,6 +29,6 @@ pub fn get_member_expr_name(expr: &Expression) -> Result { let prop = member.property.name.to_string(); Ok(format!("{}.{}", obj, prop)) } - _ => Err(anyhow!("Unsupported expression")), + _ => Err(anyhow!("Unsupported expression for name resolution")), } } diff --git a/crates/prometeu-compiler/src/codegen/codegen.rs b/crates/prometeu-compiler/src/codegen/codegen.rs index 73d80bde..83508ad0 100644 --- a/crates/prometeu-compiler/src/codegen/codegen.rs +++ b/crates/prometeu-compiler/src/codegen/codegen.rs @@ -1,31 +1,47 @@ -use anyhow::{Result, anyhow}; -use oxc_ast::ast::*; -use oxc_span::{Span, GetSpan}; -use prometeu_bytecode::opcode::OpCode; -use prometeu_bytecode::asm::{Asm, Operand, assemble}; -use prometeu_bytecode::pbc::{ConstantPoolEntry, PbcFile, write_pbc}; -use crate::compiler::Symbol; -use crate::syscall_map; use crate::codegen::ast_util; -use prometeu_core::prometeu_os::Syscall; -use std::collections::HashMap; +use crate::codegen::{input_map, syscall_map}; +use crate::compiler::Symbol; +use anyhow::{anyhow, Result}; +use oxc_ast::ast::*; +use oxc_span::{GetSpan, Span}; use prometeu_bytecode::asm; +use prometeu_bytecode::asm::{assemble, Asm, Operand}; +use prometeu_bytecode::opcode::OpCode; +use prometeu_bytecode::pbc::{write_pbc, ConstantPoolEntry, PbcFile}; +use prometeu_core::hardware::Syscall; use prometeu_core::model::Color; +use std::collections::HashMap; +/// The core Code Generator for the Prometeu Compiler. +/// +/// It maintains the state of the compilation process, including symbol tables, +/// local/global variable mapping, and the constant pool. pub struct Codegen { + /// Name of the file being compiled (used for debug symbols). file_name: String, + /// Full source code of the file (used for position lookup). source_text: String, + /// Collected debug symbols mapping PC to source lines. pub symbols: Vec, - instructions: Vec<(Asm, bool)>, // (Asm, has_symbol) + /// The stream of generated assembly instructions. + /// The boolean indicates if the instruction should have a debug symbol attached. + instructions: Vec<(Asm, bool)>, + /// Mapping of local variable names to their stack offsets in the current frame. locals: HashMap, + /// Mapping of global variable names to their slots in the VM's global memory. globals: HashMap, + /// The Constant Pool, which stores unique values (strings, large numbers). constant_pool: Vec, + /// Counter for the next available local variable ID. next_local: u32, + /// Counter for the next available global variable ID. next_global: u32, + /// Counter for generating unique labels (e.g., for 'if' or 'while' blocks). label_count: u32, } impl Codegen { + /// Creates a new Codegen instance for a specific file. pub fn new(file_name: String, source_text: String) -> Self { Self { file_name, @@ -41,6 +57,8 @@ impl Codegen { } } + /// Adds a value to the Constant Pool and returns its index. + /// If the value already exists, it returns the existing index. fn add_constant(&mut self, entry: ConstantPoolEntry) -> u32 { if let Some(pos) = self.constant_pool.iter().position(|e| e == &entry) { pos as u32 @@ -51,53 +69,54 @@ impl Codegen { } } + /// Entry point for compiling a single Program (AST). pub fn compile_program(&mut self, program: &Program) -> Result> { self.compile_programs(vec![(self.file_name.clone(), self.source_text.clone(), program)]) } + /// Compiles multiple programs (files) into a single Prometeu executable (.pbc). + /// + /// The compilation process follows several steps: + /// 1. Collection of global symbols (functions and variables). + /// 2. Entry point verification (`frame` function). + /// 3. Global variable initialization. + /// 4. Main system loop generation. + /// 5. Compilation of all function bodies. + /// 6. Assembly and symbol resolution. pub fn compile_programs(&mut self, programs: Vec<(String, String, &Program)>) -> Result> { - // First pass: collect all functions and global variables + // --- FIRST PASS: Global Functions and Variables Collection --- + // In this stage, we iterate through all files to register the existence of functions + // and global variables before starting code generation. This allows a + // function to call another that was defined later or in another file. let mut all_functions = Vec::new(); for (file, source, program) in &programs { for item in &program.body { match item { + // Standard function declaration: function foo() { ... } Statement::FunctionDeclaration(f) => { all_functions.push((file.clone(), source.clone(), f.as_ref())); } + // Exported declaration: export function foo() ... or export const x = 1; Statement::ExportNamedDeclaration(decl) => { if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration { all_functions.push((file.clone(), source.clone(), f.as_ref())); } else if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration { - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - if !self.globals.contains_key(&name) { - let id = self.next_global; - self.globals.insert(name, id); - self.next_global += 1; - } - } - } + self.export_global_variable_declarations(&var); } } + // Global variable declaration: var x = 1; Statement::VariableDeclaration(var) => { - for decl in &var.declarations { - if let BindingPattern::BindingIdentifier(ident) = &decl.id { - let name = ident.name.to_string(); - if !self.globals.contains_key(&name) { - let id = self.next_global; - self.globals.insert(name, id); - self.next_global += 1; - } - } - } + self.export_global_variable_declarations(&var); } _ => {} } } } - // Find frame function (should be in the first program, which is the entry) + // --- ENTRY POINT VERIFICATION --- + // Prometeu requires a function named `frame()` in the main file (the first in the list). + // This function is called automatically every logical frame, which is supposed to + // be close to the tick (60Hz). let mut frame_fn_name = None; if let Some((_, _, entry_program)) = programs.first() { for item in &entry_program.body { @@ -126,7 +145,9 @@ impl Codegen { let frame_fn_name = frame_fn_name.ok_or_else(|| anyhow!("export function frame() not found in entry file"))?; - // Initialize globals + // --- GLOBAL INITIALIZATION --- + // Here we generate the code that will be executed as soon as the program loads. + // It evaluates the initialization expressions of global variables and stores them. for (file, source, program) in &programs { self.file_name = file.clone(); self.source_text = source.clone(); @@ -149,10 +170,13 @@ impl Codegen { let name = ident.name.to_string(); let id = *self.globals.get(&name).unwrap(); if let Some(init) = &decl.init { + // Compiles the initialization expression (e.g., the "10" in "var x = 10") self.compile_expr(init)?; } else { + // If there is no initializer, it defaults to 0 (I32) self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span); } + // Stores the resulting value in the global variable slot self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], decl.span); } } @@ -160,39 +184,74 @@ impl Codegen { } } - // Entry point: loop calling frame + // --- EXECUTION LOOP GENERATION (ENTRY POINT) --- + // After initializing globals, the code enters an infinite loop that: + // 1. Calls the frame() function. + // 2. Clears the return value from the stack (Prometeu ABI requires functions to leave 1 value). + // 3. Synchronizes with hardware (FrameSync - waits for the next VSync). + // 4. Jumps back to the start of the loop. self.emit_label("entry".to_string()); self.emit_op(OpCode::Call, vec![Operand::Label(frame_fn_name), Operand::U32(0)], Span::default()); self.emit_op(OpCode::Pop, vec![], Span::default()); self.emit_op(OpCode::FrameSync, vec![], Span::default()); self.emit_op(OpCode::Jmp, vec![Operand::Label("entry".to_string())], Span::default()); - // Compile all functions + // --- FUNCTION COMPILATION --- + // Compiles the body of each function collected in the first pass. for (file, source, f) in all_functions { self.file_name = file; self.source_text = source; if let Some(ident) = &f.id { let name = ident.name.to_string(); + // Each function starts with a label with its name to be resolved by Call self.emit_label(name); self.compile_function(f)?; + // Ensures the function returns to the caller self.emit_op(OpCode::Ret, vec![], Span::default()); } } + // --- FINAL PHASE: ASSEMBLY AND PACKAGING --- + + // 1. Converts the list of instructions into raw bytecode let asm_vec: Vec = self.instructions.iter().map(|(a, _)| a.clone()).collect(); let bytecode = assemble(&asm_vec).map_err(|e| anyhow!("Assemble error: {}", e))?; - // Finalize symbols (associate PC) + // 2. Resolves label addresses to real bytecode positions (PC) self.finalize_symbols(); + // 3. Builds the PBC file structure (Constant Pool + Bytecode) let pbc = PbcFile { cp: self.constant_pool.clone(), rom: bytecode, }; + // 4. Serializes to the final binary format write_pbc(&pbc).map_err(|e| anyhow!("PBC Write error: {}", e)) } + /// Registers a global variable in the symbol table. + /// Global variables are accessible from any function and persist between frames. + fn export_global_variable_declarations(&mut self, var: &VariableDeclaration) { + for decl in &var.declarations { + if let BindingPattern::BindingIdentifier(ident) = &decl.id { + let name = ident.name.to_string(); + if !self.globals.contains_key(&name) { + let id = self.next_global; + self.globals.insert(name, id); + self.next_global += 1; + } + } + } + } + + /// Compiles a function declaration. + /// + /// Functions in Prometeu follow the ABI: + /// 1. Parameters are mapped to the first `n` local slots. + /// 2. `PushScope` is called to protect the caller's environment. + /// 3. The body is compiled sequentially. + /// 4. `PopScope` and `Push Null` are executed before `Ret` to ensure the stack rule. fn compile_function(&mut self, f: &Function) -> Result<()> { self.locals.clear(); self.next_local = 0; @@ -200,7 +259,7 @@ impl Codegen { // Start scope for parameters and local variables self.emit_op(OpCode::PushScope, vec![], f.span); - // Map parameters to locals + // Map parameters to locals (they are pushed by the caller before the Call instruction) for param in &f.params.items { if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern { let name = ident.name.to_string(); @@ -218,15 +277,15 @@ impl Codegen { // ABI Rule: Every function MUST leave exactly one value on the stack before RET. // If the function doesn't have a return statement, we push Null. - // For now, we always push Null at the end if it's not a return. - // In a more complex compiler, we would check if all paths return. self.emit_op(OpCode::PopScope, vec![], Span::default()); self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], Span::default()); // Index 0 is Null in PBC Ok(()) } + /// Translates a Statement into bytecode. fn compile_stmt(&mut self, stmt: &Statement) -> Result<()> { match stmt { + // var x = 10; Statement::VariableDeclaration(var) => { for decl in &var.declarations { if let BindingPattern::BindingIdentifier(ident) = &decl.id { @@ -234,6 +293,7 @@ impl Codegen { if let Some(init) = &decl.init { self.compile_expr(init)?; } else { + // Default initialization to 0 self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span); } let id = self.next_local; @@ -242,10 +302,13 @@ impl Codegen { } } } + // console.log("hello"); Statement::ExpressionStatement(expr_stmt) => { self.compile_expr(&expr_stmt.expression)?; + // ABI requires us to Pop unused return values from the stack to prevent leaks self.emit_op(OpCode::Pop, vec![], expr_stmt.span); } + // if (a == b) { ... } else { ... } Statement::IfStatement(if_stmt) => { let else_label = self.new_label("else"); let end_label = self.new_label("end_if"); @@ -263,6 +326,7 @@ impl Codegen { self.emit_label(end_label); } + // { let x = 1; } Statement::BlockStatement(block) => { self.emit_op(OpCode::PushScope, vec![], block.span); for stmt in &block.body { @@ -275,8 +339,11 @@ impl Codegen { Ok(()) } + /// Translates an Expression into bytecode. + /// Expressions always leave exactly one value at the top of the stack. fn compile_expr(&mut self, expr: &Expression) -> Result<()> { match expr { + // Literals: push the value directly onto the stack Expression::NumericLiteral(n) => { let val = n.value; if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 { @@ -295,6 +362,7 @@ impl Codegen { Expression::NullLiteral(n) => { self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], n.span); } + // Variable access: resolve to GetLocal or GetGlobal Expression::Identifier(ident) => { let name = ident.name.to_string(); if let Some(&id) = self.locals.get(&name) { @@ -305,13 +373,14 @@ impl Codegen { return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span)); } } + // Assignment: evaluate RHS and store result in LHS slot Expression::AssignmentExpression(assign) => { if let AssignmentTarget::AssignmentTargetIdentifier(ident) = &assign.left { let name = ident.name.to_string(); self.compile_expr(&assign.right)?; if let Some(&id) = self.locals.get(&name) { self.emit_op(OpCode::SetLocal, vec![Operand::U32(id)], assign.span); - self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], assign.span); + self.emit_op(OpCode::GetLocal, vec![Operand::U32(id)], assign.span); // Assignment returns the value } else if let Some(&id) = self.globals.get(&name) { self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], assign.span); self.emit_op(OpCode::GetGlobal, vec![Operand::U32(id)], assign.span); @@ -322,6 +391,7 @@ impl Codegen { return Err(anyhow!("Unsupported assignment target at {:?}", assign.span)); } } + // Binary operations: evaluate both sides and apply the opcode Expression::BinaryExpression(bin) => { self.compile_expr(&bin.left)?; self.compile_expr(&bin.right)?; @@ -340,6 +410,7 @@ impl Codegen { }; self.emit_op(op, vec![], bin.span); } + // Logical operations: evaluate both sides and apply the opcode Expression::LogicalExpression(log) => { self.compile_expr(&log.left)?; self.compile_expr(&log.right)?; @@ -350,6 +421,7 @@ impl Codegen { }; self.emit_op(op, vec![], log.span); } + // Unary operations: evaluate argument and apply the opcode Expression::UnaryExpression(unary) => { self.compile_expr(&unary.argument)?; let op = match unary.operator { @@ -360,19 +432,18 @@ impl Codegen { }; self.emit_op(op, vec![], unary.span); } + // Function calls: resolve to Syscall or Call Expression::CallExpression(call) => { let name = ast_util::get_callee_name(&call.callee)?; if let Some(syscall_id) = syscall_map::map_syscall(&name) { if syscall_id == 0xFFFF_FFFF { - // Color.rgb(r, g, b) + // Special case for Color.rgb(r, g, b) + // It's compiled to a sequence of bitwise operations for performance if call.arguments.len() != 3 { return Err(anyhow!("Color.rgb expects 3 arguments at {:?}", call.span)); } - // We'll emit the bit manipulation logic here or just a special syscall if we had one. - // Since we have bitwise opcodes, let's use them! - // ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3) - // Argument 0: r + // Argument 0: r (shift right 3, shift left 11) if let Some(expr) = call.arguments[0].as_expression() { self.compile_expr(expr)?; self.emit_op(OpCode::PushI32, vec![Operand::I32(3)], call.span); @@ -381,7 +452,7 @@ impl Codegen { self.emit_op(OpCode::Shl, vec![], call.span); } - // Argument 1: g + // Argument 1: g (shift right 2, shift left 5) if let Some(expr) = call.arguments[1].as_expression() { self.compile_expr(expr)?; self.emit_op(OpCode::PushI32, vec![Operand::I32(2)], call.span); @@ -392,7 +463,7 @@ impl Codegen { self.emit_op(OpCode::BitOr, vec![], call.span); - // Argument 2: b + // Argument 2: b (shift right 3) if let Some(expr) = call.arguments[2].as_expression() { self.compile_expr(expr)?; self.emit_op(OpCode::PushI32, vec![Operand::I32(3)], call.span); @@ -402,6 +473,7 @@ impl Codegen { self.emit_op(OpCode::BitOr, vec![], call.span); } else { + // Standard System Call for arg in &call.arguments { if let Some(expr) = arg.as_expression() { self.compile_expr(expr)?; @@ -410,7 +482,7 @@ impl Codegen { self.emit_op(OpCode::Syscall, vec![Operand::U32(syscall_id)], call.span); } } else { - // Local function call + // Local function call (to a function defined in the project) for arg in &call.arguments { if let Some(expr) = arg.as_expression() { self.compile_expr(expr)?; @@ -419,10 +491,12 @@ impl Codegen { self.emit_op(OpCode::Call, vec![Operand::Label(name), Operand::U32(call.arguments.len() as u32)], call.span); } } + // Member access (e.g., Color.RED, Pad.A.down) Expression::StaticMemberExpression(member) => { let full_name = ast_util::get_member_expr_name(expr)?; if full_name.to_lowercase().starts_with("color.") { + // Resolved at compile-time to literal values match full_name.to_lowercase().as_str() { "color.black" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::BLACK.raw() as i32)], member.span), "color.white" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::WHITE.raw() as i32)], member.span), @@ -435,15 +509,16 @@ impl Codegen { "color.orange" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::ORANGE.raw() as i32)], member.span), "color.indigo" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::INDIGO.raw() as i32)], member.span), "color.magenta" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::MAGENTA.raw() as i32)], member.span), - "color.colorKey" | "color.color_key" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::COLOR_KEY.raw() as i32)], member.span), + "color.colorkey" | "color.color_key" => self.emit_op(OpCode::PushI32, vec![Operand::I32(Color::COLOR_KEY.raw() as i32)], member.span), _ => return Err(anyhow!("Unsupported color constant: {} at {:?}", full_name, member.span)), } } else if full_name.to_lowercase().starts_with("pad.") { + // Re-mapped to specific input syscalls let parts: Vec<&str> = full_name.split('.').collect(); if parts.len() == 3 { let btn_name = parts[1]; let state_name = parts[2]; - let btn_id = self.map_btn_name(btn_name)?; + let btn_id = input_map::map_btn_name(btn_name)?; let syscall_id = match state_name { "down" => Syscall::InputGetPad as u32, "pressed" => Syscall::InputGetPadPressed as u32, @@ -457,6 +532,7 @@ impl Codegen { return Err(anyhow!("Partial Pad access not supported: {} at {:?}", full_name, member.span)); } } else if full_name.to_lowercase().starts_with("touch.") { + // Re-mapped to specific touch syscalls let parts: Vec<&str> = full_name.split('.').collect(); match parts.len() { 2 => { @@ -490,39 +566,25 @@ impl Codegen { Ok(()) } - fn map_btn_name(&self, btn_name: &str) -> Result { - match btn_name.to_lowercase().as_str() { - "up" => Ok(syscall_map::BTN_UP), - "down" => Ok(syscall_map::BTN_DOWN), - "left" => Ok(syscall_map::BTN_LEFT), - "right" => Ok(syscall_map::BTN_RIGHT), - "a" => Ok(syscall_map::BTN_A), - "b" => Ok(syscall_map::BTN_B), - "x" => Ok(syscall_map::BTN_X), - "y" => Ok(syscall_map::BTN_Y), - "l" => Ok(syscall_map::BTN_L), - "r" => Ok(syscall_map::BTN_R), - "start" => Ok(syscall_map::BTN_START), - "select" => Ok(syscall_map::BTN_SELECT), - _ => Err(anyhow!("Unsupported button: {}", btn_name)), - } - } - + /// Generates a new unique label name for control flow. fn new_label(&mut self, prefix: &str) -> String { let label = format!("{}_{}", prefix, self.label_count); self.label_count += 1; label } + /// Emits a label definition in the instruction stream. fn emit_label(&mut self, name: String) { self.instructions.push((Asm::Label(name), false)); } + /// Emits an OpCode and its operands into the instruction stream. + /// Also records a debug symbol if the source span is available. fn emit_op(&mut self, opcode: OpCode, operands: Vec, span: Span) { let has_symbol = if !span.is_unspanned() { let (line, col) = self.lookup_pos(span.start); self.symbols.push(Symbol { - pc: 0, + pc: 0, // Will be resolved during finalize_symbols file: self.file_name.clone(), line, col, @@ -534,6 +596,7 @@ impl Codegen { self.instructions.push((Asm::Op(opcode, operands), has_symbol)); } + /// Converts a character position (offset) into line and column numbers. fn lookup_pos(&self, pos: u32) -> (usize, usize) { let mut line = 1; let mut col = 1; @@ -551,6 +614,9 @@ impl Codegen { (line, col) } + /// Second pass of symbol resolution. + /// After the final PC for each instruction is known (via `assemble`), + /// this function updates the `pc` field in all debug symbols. fn finalize_symbols(&mut self) { let mut current_pc = 0u32; let mut symbol_ptr = 0; @@ -564,7 +630,7 @@ impl Codegen { symbol_ptr += 1; } - current_pc += 2; + current_pc += 2; // OpCode size (u16) current_pc = asm::update_pc_by_operand(current_pc, operands); } } diff --git a/crates/prometeu-compiler/src/codegen/mod.rs b/crates/prometeu-compiler/src/codegen/mod.rs index 3d883e34..74e74cd9 100644 --- a/crates/prometeu-compiler/src/codegen/mod.rs +++ b/crates/prometeu-compiler/src/codegen/mod.rs @@ -1,5 +1,18 @@ +//! # Codegen Module +//! +//! This module is responsible for translating the High-Level AST into +//! Low-Level Prometeu ByteCode instructions. +//! +//! It consists of: +//! - **codegen.rs**: The main engine that traverses the AST and maintains the compiler state. +//! - **validator.rs**: A pre-flight check to ensure the AST only contains supported constructs. +//! - **syscall_map.rs**: A bridge between high-level JS functions and virtual machine syscall IDs. +//! - **ast_util.rs**: Convenience functions for extracting information from OXC AST nodes. + pub mod codegen; pub mod validator; pub mod ast_util; +pub mod syscall_map; +mod input_map; -pub use codegen::Codegen; \ No newline at end of file +pub use codegen::Codegen; diff --git a/crates/prometeu-compiler/src/codegen/syscall_map.rs b/crates/prometeu-compiler/src/codegen/syscall_map.rs new file mode 100644 index 00000000..1480cb49 --- /dev/null +++ b/crates/prometeu-compiler/src/codegen/syscall_map.rs @@ -0,0 +1,20 @@ +use prometeu_core::hardware::Syscall; + +/// Maps a High-Level function name to its corresponding Virtual Machine Syscall ID. +/// +/// The Prometeu VM exposes hardware functionality (Graphics, Audio, I/O) through +/// a syscall interface. This function allows the compiler to recognize these +/// calls and emit the `Syscall ` opcode. +pub fn map_syscall(name: &str) -> Option { + // Check if the name matches a standard syscall defined in the core firmware + if let Some(syscall) = Syscall::from_name(name) { + return Some(syscall as u32); + } + + // Handle special compiler intrinsics that don't map 1:1 to a single syscall ID. + match name { + // Color.rgb(r,g,b) is expanded to bitwise logic by the codegen + "Color.rgb" | "color.rgb" => Some(0xFFFF_FFFF), + _ => None, + } +} diff --git a/crates/prometeu-compiler/src/codegen/validator.rs b/crates/prometeu-compiler/src/codegen/validator.rs index 81c1def7..3d90bf63 100644 --- a/crates/prometeu-compiler/src/codegen/validator.rs +++ b/crates/prometeu-compiler/src/codegen/validator.rs @@ -1,23 +1,35 @@ -use oxc_ast::ast::*; -use oxc_ast_visit::{Visit, walk}; -use oxc_span::GetSpan; -use anyhow::{Result, anyhow}; -use crate::syscall_map; use crate::codegen::ast_util; +use crate::codegen::syscall_map; +use anyhow::{anyhow, Result}; +use oxc_ast::ast::*; +use oxc_ast_visit::{walk, Visit}; +use oxc_span::GetSpan; +/// AST Visitor that ensures the source code follows the Prometeu subset of JS/TS. +/// +/// Since the Prometeu Virtual Machine is highly optimized and focused on game logic, +/// it does not support the full ECMA-262 specification (e.g., no `async`, `class`, +/// `try/catch`, or `generators`). pub struct Validator { + /// List of validation errors found during the pass. errors: Vec, + /// Set of function names defined in the project, used to distinguish + /// local calls from invalid references. local_functions: std::collections::HashSet, } impl Validator { + /// Performs a full validation pass on a program. + /// + /// Returns `Ok(())` if the program is valid, or a combined error message + /// listing all violations. pub fn validate(program: &Program) -> Result<()> { let mut validator = Self { errors: Vec::new(), local_functions: std::collections::HashSet::new(), }; - // 1. Collect all local function names + // 1. Discovery Pass: Collect all function names and imports for item in &program.body { match item { Statement::FunctionDeclaration(f) => { @@ -53,7 +65,7 @@ impl Validator { } } - // 2. Recursive validation of the AST + // 2. Traversal Pass: Check every node for compatibility validator.visit_program(program); if validator.errors.is_empty() { @@ -65,6 +77,7 @@ impl Validator { } impl<'a> Visit<'a> for Validator { + /// Validates that only supported statements are used. fn visit_statement(&mut self, stmt: &Statement<'a>) { match stmt { Statement::VariableDeclaration(_) | @@ -74,15 +87,16 @@ impl<'a> Visit<'a> for Validator { Statement::ExportNamedDeclaration(_) | Statement::ImportDeclaration(_) | Statement::FunctionDeclaration(_) => { - // Supported + // These are the only statements the PVM handles currently. walk::walk_statement(self, stmt); } _ => { - self.errors.push(format!("Unsupported statement type at {:?}", stmt.span())); + self.errors.push(format!("Unsupported statement type at {:?}. Note: Prometeu does not support while/for loops or classes yet.", stmt.span())); } } } + /// Validates that only supported expressions are used. fn visit_expression(&mut self, expr: &Expression<'a>) { match expr { Expression::NumericLiteral(_) | @@ -96,11 +110,11 @@ impl<'a> Visit<'a> for Validator { Expression::UnaryExpression(_) | Expression::CallExpression(_) | Expression::StaticMemberExpression(_) => { - // Base types supported, detailed checks in specific visit methods if needed + // Basic JS logic is supported. walk::walk_expression(self, expr); } _ => { - self.errors.push(format!("Unsupported expression type at {:?}", expr.span())); + self.errors.push(format!("Unsupported expression type at {:?}. Note: Closures, arrow functions, and object literals are not yet supported.", expr.span())); } } } diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index 4a0defc8..95abcea1 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -1,43 +1,62 @@ -use std::path::{Path, PathBuf}; -use anyhow::{Result, Context, anyhow}; +use crate::codegen::validator::Validator; +use crate::codegen::Codegen; +use anyhow::{anyhow, Context, Result}; use oxc_allocator::Allocator; +use oxc_ast::ast::*; use oxc_parser::Parser; use oxc_span::SourceType; -use oxc_ast::ast::*; -use crate::codegen::Codegen; -use crate::codegen::validator::Validator; -use std::fs; -use std::collections::{HashMap, VecDeque}; use prometeu_bytecode::disasm::disasm; use serde::Serialize; +use std::collections::{HashMap, VecDeque}; +use std::fs; +use std::path::{Path, PathBuf}; +/// Represents a debug symbol that maps a Program Counter (PC) to a specific +/// source code location. #[derive(Serialize)] pub struct Symbol { + /// The absolute address in the bytecode. pub pc: u32, + /// The original source file path. pub file: String, + /// 1-based line number. pub line: usize, + /// 1-based column number. pub col: usize, } +/// The result of a successful compilation process. +/// It contains the final binary and the metadata needed for debugging. pub struct CompilationUnit { + /// The raw binary data (PBC format). pub rom: Vec, + /// The list of debug symbols for source mapping. pub symbols: Vec, } impl CompilationUnit { + /// Writes the compilation results to the disk. + /// + /// # Arguments + /// * `out` - The target path for the .pbc file. + /// * `emit_disasm` - If true, generates a .disasm.txt file next to the output. + /// * `emit_symbols` - If true, generates a symbols.json file for debuggers. pub fn export(&self, out: &Path, emit_disasm: bool, emit_symbols: bool) -> Result<()> { + // 1. Save the main binary fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?; + // 2. Export symbols for the HostDebugger if emit_symbols { let symbols_path = out.with_file_name("symbols.json"); let symbols_json = serde_json::to_string_pretty(&self.symbols)?; fs::write(&symbols_path, symbols_json)?; } + // 3. Export human-readable disassembly for developer inspection if emit_disasm { let disasm_path = out.with_extension("disasm.txt"); - // Try to parse as PBC, if fails use raw + // Extract the actual bytecode (stripping the PBC header if present) let rom_to_disasm = if let Ok(pbc) = prometeu_bytecode::pbc::parse_pbc(&self.rom) { pbc.rom } else { @@ -48,6 +67,7 @@ impl CompilationUnit { let mut disasm_text = String::new(); for instr in instructions { + // Find a matching symbol to show which source line generated this instruction let symbol = self.symbols.iter().find(|s| s.pc == instr.pc); let comment = if let Some(s) = symbol { format!(" ; {}:{}", s.file, s.line) @@ -69,8 +89,11 @@ impl CompilationUnit { } } +/// Helper to resolve import paths (e.g., converting './utils' to './utils.ts'). fn resolve_import(base_path: &Path, import_str: &str) -> Result { let mut path = base_path.parent().unwrap().join(import_str); + + // Auto-append extensions if missing if !path.exists() { if path.with_extension("ts").exists() { path.set_extension("ts"); @@ -78,36 +101,48 @@ fn resolve_import(base_path: &Path, import_str: &str) -> Result { path.set_extension("js"); } } + if !path.exists() { return Err(anyhow!("Cannot resolve import '{}' from {:?}", import_str, base_path)); } + Ok(path.canonicalize()?) } +/// Orchestrates the compilation of a Prometeu project starting from an entry file. +/// +/// This function: +/// 1. Crawls the import graph to find all dependencies. +/// 2. Parses each file using `oxc`. +/// 3. Validates that the code adheres to Prometeu's restricted JS/TS subset. +/// 4. Hands over all programs to the `Codegen` for binary generation. pub fn compile(entry: &Path) -> Result { let allocator = Allocator::default(); let mut modules = HashMap::new(); let mut queue = VecDeque::new(); + // Start with the entry point let entry_abs = entry.canonicalize() .with_context(|| format!("Failed to canonicalize entry path: {:?}", entry))?; queue.push_back(entry_abs.clone()); + // --- PHASE 1: Dependency Resolution and Parsing --- while let Some(path) = queue.pop_front() { let path_str = path.to_string_lossy().to_string(); if modules.contains_key(&path_str) { - continue; + continue; // Already processed } let source_text = fs::read_to_string(&path) .with_context(|| format!("Failed to read file: {:?}", path))?; - // Allocate source_text in the allocator to keep it alive + // We use an Allocator to manage the lifetime of AST nodes and source strings efficiently let source_text_ptr: &str = allocator.alloc_str(&source_text); let source_type = SourceType::from_path(&path).unwrap_or_default(); let parser_ret = Parser::new(&allocator, source_text_ptr, source_type).parse(); + // Handle syntax errors immediately if !parser_ret.errors.is_empty() { for error in parser_ret.errors { eprintln!("{:?}", error); @@ -115,10 +150,11 @@ pub fn compile(entry: &Path) -> Result { return Err(anyhow!("Failed to parse module: {:?}", path)); } - // Validate individual module + // --- PHASE 2: Individual Module Validation --- + // Checks for things like 'class' or 'async' which are not supported by PVM. Validator::validate(&parser_ret.program)?; - // Find imports to add to queue + // Discover new imports to crawl for item in &parser_ret.program.body { if let Statement::ImportDeclaration(decl) = item { let import_path = decl.source.value.as_str(); @@ -130,14 +166,15 @@ pub fn compile(entry: &Path) -> Result { modules.insert(path_str, (source_text_ptr, parser_ret.program)); } + // --- PHASE 3: Code Generation --- let entry_str = entry_abs.to_string_lossy().to_string(); let mut program_list = Vec::new(); - // Add entry program first + // Ensure the entry module (containing 'frame()') is processed first let entry_data = modules.get(&entry_str).ok_or_else(|| anyhow!("Entry module not found after loading"))?; program_list.push((entry_str.clone(), entry_data.0.to_string(), &entry_data.1)); - // Add all other programs + // Collect all other modules for (path, (source, program)) in &modules { if path != &entry_str { program_list.push((path.clone(), source.to_string(), program)); diff --git a/crates/prometeu-compiler/src/lib.rs b/crates/prometeu-compiler/src/lib.rs index 7916341d..8167207b 100644 --- a/crates/prometeu-compiler/src/lib.rs +++ b/crates/prometeu-compiler/src/lib.rs @@ -1,16 +1,75 @@ -pub mod cli; +//! # Prometeu Compiler +//! +//! This crate provides the official compiler for the Prometeu ecosystem. +//! It translates TypeScript/JavaScript source code into Prometeu ByteCode (.pbc). +//! +//! ## Workflow: +//! 1. **Parsing**: Uses the `oxc` parser to generate an Abstract Syntax Tree (AST). +//! 2. **Validation**: Checks for unsupported features and ensures ABI compliance. +//! 3. **Codegen**: Traverses the AST and emits `prometeu-bytecode` instructions. +//! 4. **Assembly**: Resolves labels and serializes the program into the binary PBC format. +//! +//! ## Example Usage: +//! ```bash +//! # Build a project +//! prometeu-compiler build ./my-game --entry ./src/main.ts --out ./game.pbc +//! ``` + pub mod codegen; pub mod compiler; -pub mod syscall_map; -use clap::Parser; use anyhow::Result; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; +/// Command line interface for the Prometeu Compiler. +#[derive(Parser)] +#[command(name = "prometeu-compiler")] +#[command(version, about = "Official compiler for the PROMETEU Virtual Machine", long_about = None)] +pub struct Cli { + /// The action to perform (build or verify). + #[command(subcommand)] + pub command: Commands, +} + +/// Available subcommands for the compiler. +#[derive(Subcommand)] +pub enum Commands { + /// Builds a Prometeu project by compiling source code into a PBC file. + Build { + /// Path to the project root directory. + project_dir: PathBuf, + + /// Explicit path to the entry file (defaults to src/main.ts). + #[arg(short, long)] + entry: Option, + + /// Path to save the compiled .pbc file. + #[arg(short, long)] + out: Option, + + /// Whether to generate a .disasm file for debugging. + #[arg(long, default_value_t = true)] + emit_disasm: bool, + + /// Whether to generate a .json symbols file for source mapping. + #[arg(long, default_value_t = true)] + emit_symbols: bool, + }, + /// Verifies if a Prometeu project is syntactically and semantically valid without emitting code. + Verify { + /// Path to the project root directory. + project_dir: PathBuf, + }, +} + +/// Main entry point for the compiler library's execution logic. +/// Parses CLI arguments and dispatches to the appropriate compiler functions. pub fn run() -> Result<()> { - let cli = cli::Cli::parse(); + let cli = Cli::parse(); match cli.command { - cli::Commands::Build { + Commands::Build { project_dir, entry, out, @@ -32,7 +91,7 @@ pub fn run() -> Result<()> { let compilation_unit = compiler::compile(&entry)?; compilation_unit.export(&out, emit_disasm, emit_symbols)?; } - cli::Commands::Verify { project_dir } => { + Commands::Verify { project_dir } => { let entry = project_dir.join("src/main.ts"); println!("Verifying project at {:?}", project_dir); println!("Entry: {:?}", entry); diff --git a/crates/prometeu-compiler/src/main.rs b/crates/prometeu-compiler/src/main.rs index 6433df4a..a57baf4b 100644 --- a/crates/prometeu-compiler/src/main.rs +++ b/crates/prometeu-compiler/src/main.rs @@ -1,5 +1,7 @@ use anyhow::Result; +/// Main entry point for the Prometeu Compiler binary. +/// It delegates execution to the library's `run` function. fn main() -> Result<()> { prometeu_compiler::run() } diff --git a/crates/prometeu-compiler/src/syscall_map.rs b/crates/prometeu-compiler/src/syscall_map.rs deleted file mode 100644 index df0779ee..00000000 --- a/crates/prometeu-compiler/src/syscall_map.rs +++ /dev/null @@ -1,27 +0,0 @@ -use prometeu_core::prometeu_os::Syscall; -use prometeu_core::model::ButtonId; - -pub const BTN_UP: u32 = ButtonId::Up as u32; -pub const BTN_DOWN: u32 = ButtonId::Down as u32; -pub const BTN_LEFT: u32 = ButtonId::Left as u32; -pub const BTN_RIGHT: u32 = ButtonId::Right as u32; -pub const BTN_A: u32 = ButtonId::A as u32; -pub const BTN_B: u32 = ButtonId::B as u32; -pub const BTN_X: u32 = ButtonId::X as u32; -pub const BTN_Y: u32 = ButtonId::Y as u32; -pub const BTN_L: u32 = ButtonId::L as u32; -pub const BTN_R: u32 = ButtonId::R as u32; -pub const BTN_START: u32 = ButtonId::Start as u32; -pub const BTN_SELECT: u32 = ButtonId::Select as u32; - -pub fn map_syscall(name: &str) -> Option { - if let Some(syscall) = Syscall::from_name(name) { - return Some(syscall as u32); - } - - // Fallback para nomes especiais do compilador - match name { - "Color.rgb" | "color.rgb" => Some(0xFFFF_FFFF), // ID especial para Color.rgb (não é um syscall real) - _ => None, - } -} diff --git a/crates/prometeu-core/src/debugger_protocol.rs b/crates/prometeu-core/src/debugger_protocol.rs index e154bcbe..31bfbd58 100644 --- a/crates/prometeu-core/src/debugger_protocol.rs +++ b/crates/prometeu-core/src/debugger_protocol.rs @@ -1,5 +1,5 @@ -use serde::{Deserialize, Serialize}; use crate::model::AppMode; +use serde::{Deserialize, Serialize}; use crate::virtual_machine::Value; @@ -80,6 +80,7 @@ pub enum DebugEvent { vm_steps: u32, syscalls: u32, cycles: u64, + cycles_budget: u64, host_cpu_time_us: u64, violations: u32, gfx_used_bytes: usize, @@ -103,6 +104,30 @@ mod tests { use super::*; use crate::virtual_machine::Value; + #[test] + fn test_telemetry_event_serialization() { + let event = DebugEvent::Telemetry { + frame_index: 10, + vm_steps: 100, + syscalls: 5, + cycles: 5000, + cycles_budget: 10000, + host_cpu_time_us: 1200, + violations: 0, + gfx_used_bytes: 1024, + gfx_inflight_bytes: 0, + gfx_slots_occupied: 1, + audio_used_bytes: 2048, + audio_inflight_bytes: 0, + audio_slots_occupied: 2, + }; + + let json = serde_json::to_string(&event).unwrap(); + assert!(json.contains("\"event\":\"telemetry\"")); + assert!(json.contains("\"cycles\":5000")); + assert!(json.contains("\"cycles_budget\":10000")); + } + #[test] fn test_get_state_serialization() { let resp = DebugResponse::GetState { diff --git a/crates/prometeu-core/src/firmware/firmware.rs b/crates/prometeu-core/src/firmware/firmware.rs index a91b3f46..25169e8e 100644 --- a/crates/prometeu-core/src/firmware/firmware.rs +++ b/crates/prometeu-core/src/firmware/firmware.rs @@ -11,21 +11,31 @@ use crate::telemetry::CertificationConfig; /// PROMETEU Firmware. /// -/// The central orchestrator of the system. It manages the high-level state machine, -/// transitioning between system states like booting, the Hub launcher, and -/// actual application execution. +/// The central orchestrator of the console. The firmware acts as the +/// "Control Unit", managing the high-level state machine of the system. +/// +/// It is responsible for transitioning between different modes of operation, +/// such as showing the splash screen, running the Hub (launcher), or +/// executing a game/app. +/// +/// ### Execution Loop: +/// The firmware is designed to be ticked once per frame (60Hz). During each +/// tick, it: +/// 1. Updates peripherals with the latest input signals. +/// 2. Delegates the logic update to the current active state. +/// 3. Handles state transitions (e.g., from Loading to Playing). pub struct Firmware { - /// The Virtual Machine instance. + /// The execution engine (PVM) for user applications. pub vm: VirtualMachine, - /// The System Operating logic (syscalls, telemetry, logs). + /// The underlying OS services (Syscalls, Filesystem, Telemetry). pub os: PrometeuOS, - /// The System UI / Launcher environment. + /// The internal state of the system launcher (Hub). pub hub: PrometeuHub, - /// Current high-level state of the system. + /// The current operational state (e.g., Reset, SplashScreen, GameRunning). pub state: FirmwareState, - /// Desired execution target resolved at boot. + /// The desired application to run after boot (Hub or specific Cartridge). pub boot_target: BootTarget, - /// Tracking flag to ensure `on_enter` is called exactly once per state transition. + /// State-machine lifecycle tracker. state_initialized: bool, } diff --git a/crates/prometeu-core/src/firmware/firmware_state.rs b/crates/prometeu-core/src/firmware/firmware_state.rs index da2d1228..d4bad12f 100644 --- a/crates/prometeu-core/src/firmware/firmware_state.rs +++ b/crates/prometeu-core/src/firmware/firmware_state.rs @@ -1,10 +1,10 @@ +pub use crate::firmware::firmware_step_crash_screen::AppCrashesStep; +pub use crate::firmware::firmware_step_game_running::GameRunningStep; +pub use crate::firmware::firmware_step_hub_home::HubHomeStep; +pub use crate::firmware::firmware_step_launch_hub::LaunchHubStep; +pub use crate::firmware::firmware_step_load_cartridge::LoadCartridgeStep; pub use crate::firmware::firmware_step_reset::ResetStep; pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep; -pub use crate::firmware::firmware_step_launch_hub::LaunchHubStep; -pub use crate::firmware::firmware_step_hub_home::HubHomeStep; -pub use crate::firmware::firmware_step_load_cartridge::LoadCartridgeStep; -pub use crate::firmware::firmware_step_game_running::GameRunningStep; -pub use crate::firmware::firmware_step_crash_screen::AppCrashesStep; #[derive(Debug, Clone)] pub enum FirmwareState { diff --git a/crates/prometeu-core/src/firmware/firmware_step_crash_screen.rs b/crates/prometeu-core/src/firmware/firmware_step_crash_screen.rs index d7c94a83..7bf02910 100644 --- a/crates/prometeu-core/src/firmware/firmware_step_crash_screen.rs +++ b/crates/prometeu-core/src/firmware/firmware_step_crash_screen.rs @@ -1,7 +1,7 @@ use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep}; use crate::firmware::prometeu_context::PrometeuContext; -use crate::model::Color; use crate::log::{LogLevel, LogSource}; +use crate::model::Color; #[derive(Debug, Clone)] pub struct AppCrashesStep { diff --git a/crates/prometeu-core/src/firmware/firmware_step_launch_hub.rs b/crates/prometeu-core/src/firmware/firmware_step_launch_hub.rs index 066cb56f..6a9ef3b4 100644 --- a/crates/prometeu-core/src/firmware/firmware_step_launch_hub.rs +++ b/crates/prometeu-core/src/firmware/firmware_step_launch_hub.rs @@ -1,8 +1,8 @@ use crate::firmware::boot_target::BootTarget; use crate::firmware::firmware_state::{FirmwareState, HubHomeStep, LoadCartridgeStep}; use crate::firmware::prometeu_context::PrometeuContext; -use crate::model::CartridgeLoader; use crate::log::{LogLevel, LogSource}; +use crate::model::CartridgeLoader; #[derive(Debug, Clone)] pub struct LaunchHubStep; diff --git a/crates/prometeu-core/src/firmware/firmware_step_load_cartridge.rs b/crates/prometeu-core/src/firmware/firmware_step_load_cartridge.rs index c9725057..d7ca4ca7 100644 --- a/crates/prometeu-core/src/firmware/firmware_step_load_cartridge.rs +++ b/crates/prometeu-core/src/firmware/firmware_step_load_cartridge.rs @@ -1,7 +1,7 @@ use crate::firmware::firmware_state::{FirmwareState, GameRunningStep, HubHomeStep}; use crate::firmware::prometeu_context::PrometeuContext; -use crate::model::{AppMode, Cartridge, Color, Rect}; use crate::log::{LogLevel, LogSource}; +use crate::model::{AppMode, Cartridge, Color, Rect}; #[derive(Debug, Clone)] pub struct LoadCartridgeStep { diff --git a/crates/prometeu-core/src/firmware/mod.rs b/crates/prometeu-core/src/firmware/mod.rs index 48f750dc..4c71a92f 100644 --- a/crates/prometeu-core/src/firmware/mod.rs +++ b/crates/prometeu-core/src/firmware/mod.rs @@ -11,7 +11,7 @@ pub(crate) mod firmware_step_game_running; pub(crate) mod firmware_step_crash_screen; mod prometeu_context; +pub use boot_target::BootTarget; pub use firmware::Firmware; pub use firmware_state::FirmwareState; pub use prometeu_context::PrometeuContext; -pub use boot_target::BootTarget; diff --git a/crates/prometeu-core/src/fs/mod.rs b/crates/prometeu-core/src/fs/mod.rs index 7b44e66a..119667a6 100644 --- a/crates/prometeu-core/src/fs/mod.rs +++ b/crates/prometeu-core/src/fs/mod.rs @@ -4,8 +4,8 @@ mod fs_entry; mod fs_backend; mod virtual_fs; +pub use fs_backend::FsBackend; +pub use fs_entry::FsEntry; pub use fs_error::FsError; pub use fs_state::FsState; -pub use fs_entry::FsEntry; -pub use fs_backend::FsBackend; pub use virtual_fs::VirtualFS; diff --git a/crates/prometeu-core/src/fs/virtual_fs.rs b/crates/prometeu-core/src/fs/virtual_fs.rs index 4a4cff63..87d7bb2a 100644 --- a/crates/prometeu-core/src/fs/virtual_fs.rs +++ b/crates/prometeu-core/src/fs/virtual_fs.rs @@ -1,6 +1,15 @@ use crate::fs::{FsBackend, FsEntry, FsError}; +/// Virtual Filesystem (VFS) interface for Prometeu. +/// +/// The VFS provides a sandboxed, unified path interface for user applications. +/// Instead of interacting directly with the host's disk, the VM uses +/// normalized paths (e.g., `/user/save.dat`). +/// +/// The actual storage is provided by an `FsBackend`, which can be a real +/// directory on disk, an in-memory map, or even a network resource. pub struct VirtualFS { + /// The active storage implementation. backend: Option>, } diff --git a/crates/prometeu-core/src/hardware/asset.rs b/crates/prometeu-core/src/hardware/asset.rs index 5dae5fa0..d7a94f11 100644 --- a/crates/prometeu-core/src/hardware/asset.rs +++ b/crates/prometeu-core/src/hardware/asset.rs @@ -1,5 +1,5 @@ -use crate::hardware::memory_banks::{TileBankPoolInstaller, SoundBankPoolInstaller}; -use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, SlotRef, SlotStats, TileBank, TileSize, SoundBank, Sample, PreloadEntry}; +use crate::hardware::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller}; +use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, PreloadEntry, Sample, SlotRef, SlotStats, SoundBank, TileBank, TileSize}; use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; use std::thread; @@ -659,7 +659,7 @@ impl AssetManager { #[cfg(test)] mod tests { use super::*; - use crate::hardware::memory_banks::{TileBankPoolAccess, SoundBankPoolAccess, MemoryBanks}; + use crate::hardware::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess}; #[test] fn test_asset_loading_flow() { diff --git a/crates/prometeu-core/src/hardware/audio.rs b/crates/prometeu-core/src/hardware/audio.rs index 45fff287..adcc8d34 100644 --- a/crates/prometeu-core/src/hardware/audio.rs +++ b/crates/prometeu-core/src/hardware/audio.rs @@ -97,15 +97,25 @@ pub enum AudioCommand { /// PROMETEU Audio Subsystem. /// -/// Models a multi-channel PCM sampler. -/// It works like an "Audio CPU": the Game Core sends high-level commands -/// every frame, and the Host backend implements the low-level mixer. +/// Models a multi-channel PCM sampler (SPU). +/// The audio system in Prometeu is **command-based**. This means the Core +/// doesn't generate raw audio samples; instead, it sends high-level commands +/// (like `Play`, `Stop`, `SetVolume`) to a queue. The physical host backend +/// (e.g., CPAL on desktop) then consumes these commands and performs the +/// actual mixing at the native hardware sample rate. +/// +/// ### Key Features: +/// - **16 Simultaneous Voices**: Independent volume, pan, and pitch. +/// - **Sample-based Synthesis**: Plays PCM data stored in SoundBanks. +/// - **Stereo Output**: 48kHz output target. pub struct Audio { - /// Local state of the 16 hardware voices. + /// Local state of the hardware voices. This state is used for logic + /// (e.g., checking if a sound is still playing) and is synchronized with the Host. pub voices: [Channel; MAX_CHANNELS], /// Queue of pending commands to be processed by the Host mixer. + /// This queue is typically flushed and sent to the Host once per frame. pub commands: Vec, - /// Interface to access sound memory banks. + /// Interface to access sound memory banks (ARAM). pub sound_banks: Arc, } diff --git a/crates/prometeu-core/src/hardware/gfx.rs b/crates/prometeu-core/src/hardware/gfx.rs index f864c7d9..cd0306dc 100644 --- a/crates/prometeu-core/src/hardware/gfx.rs +++ b/crates/prometeu-core/src/hardware/gfx.rs @@ -4,18 +4,24 @@ use std::sync::Arc; /// Blending modes inspired by classic 16-bit hardware. /// Defines how source pixels are combined with existing pixels in the framebuffer. +/// +/// ### Usage Example: +/// ```rust +/// // Draw a semi-transparent blue rectangle +/// gfx.fill_rect_blend(10, 10, 50, 50, Color::BLUE, BlendMode::Half); +/// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum BlendMode { - /// No blending: source overwrites destination. + /// No blending: a source overwrites the destination. #[default] None, - /// Average: dst = (src + dst) / 2. Creates a semi-transparent effect. + /// Average: `dst = (src + dst) / 2`. Creates a semi-transparent effect. Half, - /// Additive: dst = dst + (src / 2). Good for glows/light. + /// Additive: `dst = clamp(dst + (src / 2))`. Good for glows/light. HalfPlus, - /// Subtractive: dst = dst - (src / 2). Good for shadows. + /// Subtractive: `dst = clamp(dst - (src / 2))`. Good for shadows. HalfMinus, - /// Full Additive: dst = dst + src. Saturated light effect. + /// Full Additive: `dst = clamp(dst + src)`. Saturated light effect. Full, } @@ -24,44 +30,48 @@ pub enum BlendMode { /// Models a specialized graphics chip with a fixed resolution, double buffering, /// and a multi-layered tile/sprite architecture. /// -/// Composition Order (back to front): -/// 1. Sprites with priority 0 (optional background objects) -/// 2. Tile Layer 0 + Sprites with priority 1 -/// 3. Tile Layer 1 + Sprites with priority 2 -/// 4. Tile Layer 2 + Sprites with priority 3 -/// 5. Tile Layer 3 + Sprites with priority 4 -/// 6. Scene Fade effect -/// 7. HUD Layer (always on top) -/// 8. HUD Fade effect +/// The GFX system works by composing several "layers" into a single 16-bit +/// RGB565 framebuffer. It supports hardware-accelerated primitives (lines, rects) +/// and specialized console features like background scrolling and sprite sorting. +/// +/// ### Layer Composition Order (back to front): +/// 1. **Priority 0 Sprites**: Objects behind everything else. +/// 2. **Tile Layer 0 + Priority 1 Sprites**: Background 0. +/// 3. **Tile Layer 1 + Priority 2 Sprites**: Background 1. +/// 4. **Tile Layer 2 + Priority 3 Sprites**: Background 2. +/// 5. **Tile Layer 3 + Priority 4 Sprites**: Foreground. +/// 6. **Scene Fade**: Global brightness/color filter. +/// 7. **HUD Layer**: Fixed UI elements (always on top). +/// 8. **HUD Fade**: Independent fade for the UI. pub struct Gfx { - /// Width of the internal framebuffer. + /// Width of the internal framebuffer in pixels. w: usize, - /// Height of the internal framebuffer. + /// Height of the internal framebuffer in pixels. h: usize, - /// Front buffer: the one currently being displayed by the Host. + /// Front buffer: the "VRAM" currently being displayed by the Host window. front: Vec, - /// Back buffer: the one being drawn to during the current frame. + /// Back buffer: the "Work RAM" where new frames are composed. back: Vec, - /// 4 scrollable backgrounds. + /// 4 scrollable background layers. Each can have its own scroll (X, Y) and TileBank. pub layers: [ScrollableTileLayer; 4], - /// 1 fixed layer for User Interface. + /// 1 fixed layer for User Interface (HUD). It doesn't scroll. pub hud: HudTileLayer, - /// Interface to access graphical memory banks. + /// Shared access to graphical memory banks (tiles and palettes). pub tile_banks: Arc, - /// Hardware sprites (Object Attribute Memory equivalent). + /// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory). pub sprites: [Sprite; 512], - /// Current transparency level for the main scene (0=invisible, 31=fully visible). + /// Scene brightness/fade level (0 = black/invisible, 31 = fully visible). pub scene_fade_level: u8, - /// Color used for the scene fade effect. + /// Target color for the scene fade effect (usually Black). pub scene_fade_color: Color, - /// Current transparency level for the HUD (independent of scene). + /// HUD brightness/fade level (independent from the scene). pub hud_fade_level: u8, - /// Color used for the HUD fade effect. + /// Target color for the HUD fade effect. pub hud_fade_color: Color, - /// Internal cache used to sort sprites by priority without allocations. + /// Internal cache used to sort sprites into priority groups to optimize rendering. priority_buckets: [Vec; 5], } diff --git a/crates/prometeu-core/src/hardware/hardware.rs b/crates/prometeu-core/src/hardware/hardware.rs index 5e7925fd..08e86dbd 100644 --- a/crates/prometeu-core/src/hardware/hardware.rs +++ b/crates/prometeu-core/src/hardware/hardware.rs @@ -1,23 +1,29 @@ -use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, Pad, Touch, MemoryBanks}; -use crate::hardware::memory_banks::{TileBankPoolAccess, TileBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller}; +use crate::hardware::memory_banks::{SoundBankPoolAccess, SoundBankPoolInstaller, TileBankPoolAccess, TileBankPoolInstaller}; +use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, MemoryBanks, Pad, Touch}; use std::sync::Arc; /// Aggregate structure for all virtual hardware peripherals. /// -/// This struct represents the "Mainboard" of the PROMETEU console, -/// containing instances of GFX, Audio, Input (Pad), and Touch. +/// This struct represents the "Mainboard" of the PROMETEU console. +/// It acts as a container for all hardware subsystems. In the Prometeu +/// architecture, hardware is decoupled from the OS and VM, allowing +/// for easier testing and different host implementations (Desktop, Web, etc.). +/// +/// ### Console Specifications: +/// - **Resolution**: 320x180 (16:9 Aspect Ratio). +/// - **Color Depth**: RGB565 (16-bit). +/// - **Audio**: Stereo, Command-based mixing. +/// - **Input**: 12-button Digital Gamepad + Absolute Touch/Mouse. pub struct Hardware { - /// Shared memory banks for hardware assets. - pub memory_banks: Arc, - /// The Graphics Processing Unit. + /// The Graphics Processing Unit (GPU). Handles drawing primitives, sprites, and tilemaps. pub gfx: Gfx, - /// The Sound Processing Unit. + /// The Sound Processing Unit (SPU). Manages sample playback and volume control. pub audio: Audio, - /// The standard digital gamepad. + /// The standard digital gamepad. Provides state for D-Pad, face buttons, and triggers. pub pad: Pad, - /// The absolute pointer input device. + /// The absolute pointer input device (Mouse/Touchscreen). pub touch: Touch, - /// The Asset Management system. + /// The Asset Management system (DMA). Handles loading data into VRAM (TileBanks) and ARAM (SoundBanks). pub assets: AssetManager, } @@ -48,7 +54,6 @@ impl Hardware { pub fn new() -> Self { let memory_banks = Arc::new(MemoryBanks::new()); Self { - memory_banks: Arc::clone(&memory_banks), gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc), audio: Audio::new(Arc::clone(&memory_banks) as Arc), pad: Pad::default(), diff --git a/crates/prometeu-core/src/hardware/memory_banks.rs b/crates/prometeu-core/src/hardware/memory_banks.rs index 2c6d86d9..fcfd9bd6 100644 --- a/crates/prometeu-core/src/hardware/memory_banks.rs +++ b/crates/prometeu-core/src/hardware/memory_banks.rs @@ -1,5 +1,5 @@ +use crate::model::{SoundBank, TileBank}; use std::sync::{Arc, RwLock}; -use crate::model::{TileBank, SoundBank}; /// Non-generic interface for peripherals to access graphical tile banks. pub trait TileBankPoolAccess: Send + Sync { diff --git a/crates/prometeu-core/src/hardware/mod.rs b/crates/prometeu-core/src/hardware/mod.rs index 8e240f35..f27a8bef 100644 --- a/crates/prometeu-core/src/hardware/mod.rs +++ b/crates/prometeu-core/src/hardware/mod.rs @@ -6,6 +6,7 @@ mod input_signal; mod audio; mod memory_banks; pub mod hardware; +pub mod syscalls; pub use crate::model::HandleId; pub use asset::AssetManager; @@ -15,6 +16,7 @@ pub use gfx::Gfx; pub use input_signal::InputSignals; pub use memory_banks::MemoryBanks; pub use pad::Pad; +pub use syscalls::Syscall; pub use touch::Touch; pub trait HardwareBridge { diff --git a/crates/prometeu-core/src/hardware/pad.rs b/crates/prometeu-core/src/hardware/pad.rs index 45db571d..2dd0e5d8 100644 --- a/crates/prometeu-core/src/hardware/pad.rs +++ b/crates/prometeu-core/src/hardware/pad.rs @@ -1,5 +1,5 @@ -use crate::model::Button; use crate::hardware::input_signal::InputSignals; +use crate::model::Button; #[derive(Default, Clone, Copy, Debug)] pub struct Pad { diff --git a/crates/prometeu-core/src/prometeu_os/syscalls.rs b/crates/prometeu-core/src/hardware/syscalls.rs similarity index 70% rename from crates/prometeu-core/src/prometeu_os/syscalls.rs rename to crates/prometeu-core/src/hardware/syscalls.rs index bc1a5b4c..9b74291a 100644 --- a/crates/prometeu-core/src/prometeu_os/syscalls.rs +++ b/crates/prometeu-core/src/hardware/syscalls.rs @@ -1,57 +1,106 @@ +/// Enumeration of all System Calls (Syscalls) available in the Prometeu environment. +/// +/// Syscalls are the primary mechanism for a program running in the Virtual Machine +/// to interact with the outside world (Hardware, OS, Filesystem). +/// +/// Each Syscall has a unique 32-bit ID. The IDs are grouped by category: +/// - **0x0xxx**: System & OS Control +/// - **0x1xxx**: Graphics (GFX) +/// - **0x2xxx**: Input (Gamepad & Touch) +/// - **0x3xxx**: Audio (PCM & Mixing) +/// - **0x4xxx**: Filesystem (Sandboxed I/O) +/// - **0x5xxx**: Logging & Debugging +/// - **0x6xxx**: Asset Loading & Memory Banks #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum Syscall { - // System + // --- System --- + /// Checks if a cartridge is currently inserted in the virtual slot. SystemHasCart = 0x0001, + /// Requests the OS to launch a specific cartridge. SystemRunCart = 0x0002, - // GFX + // --- GFX (Graphics) --- + /// Fills the entire back buffer with a single color. GfxClear = 0x1001, + /// Draws a solid rectangle. GfxFillRect = 0x1002, + /// Draws a 1-pixel wide line. GfxDrawLine = 0x1003, + /// Draws a circle outline. GfxDrawCircle = 0x1004, + /// Draws a filled circle (disc). GfxDrawDisc = 0x1005, + /// Draws a rectangle outline. GfxDrawSquare = 0x1006, + /// Configures one of the 512 hardware sprites. GfxSetSprite = 0x1007, - // Input + // --- Input --- + /// Returns the current raw state of the digital gamepad (bitmask). InputGetPad = 0x2001, + /// Returns buttons that were pressed exactly in this frame. InputGetPadPressed = 0x2002, + /// Returns buttons that were released exactly in this frame. InputGetPadReleased = 0x2003, + /// Returns how many frames a button has been held down. InputGetPadHold = 0x2004, + /// Returns the X coordinate of the touch/mouse pointer. TouchGetX = 0x2101, + /// Returns the Y coordinate of the touch/mouse pointer. TouchGetY = 0x2102, + /// Returns true if the pointer is currently touching the screen. TouchIsDown = 0x2103, + /// Returns true if the touch started in this frame. TouchIsPressed = 0x2104, + /// Returns true if the touch ended in this frame. TouchIsReleased = 0x2105, + /// Returns how many frames the pointer has been held down. TouchGetHold = 0x2106, - // Audio + // --- Audio --- + /// Starts playback of a sound sample by its Bank and ID. AudioPlaySample = 0x3001, + /// Low-level audio play command. AudioPlay = 0x3002, - // FS + // --- FS (Filesystem) --- + /// Opens a file for reading or writing. Returns a File Handle (u32). FsOpen = 0x4001, + /// Reads data from an open file handle into the VM heap. FsRead = 0x4002, + /// Writes data from the VM heap into an open file handle. FsWrite = 0x4003, + /// Closes an open file handle. FsClose = 0x4004, + /// Lists entries in a directory. FsListDir = 0x4005, + /// Checks if a file or directory exists. FsExists = 0x4006, + /// Deletes a file or empty directory. FsDelete = 0x4007, - // Log + // --- Log --- + /// Writes a generic string to the system log. LogWrite = 0x5001, + /// Writes a string to the system log with a specific numerical tag. LogWriteTag = 0x5002, - // Asset + // --- Asset (DMA) --- + /// Starts an asynchronous load of a file into a memory bank. AssetLoad = 0x6001, + /// Returns the status of a pending asset load (0=Loading, 1=Ready, 2=Error). AssetStatus = 0x6002, + /// Finalizes the asset loading, making it available for GFX/Audio. AssetCommit = 0x6003, + /// Cancels a pending asset load. AssetCancel = 0x6004, - // Bank + // --- Bank (Memory) --- + /// Returns information about a specific Memory Bank. BankInfo = 0x6101, + /// Returns information about a slot within a Memory Bank. BankSlotInfo = 0x6102, } diff --git a/crates/prometeu-core/src/hardware/touch.rs b/crates/prometeu-core/src/hardware/touch.rs index de02bccb..109c6023 100644 --- a/crates/prometeu-core/src/hardware/touch.rs +++ b/crates/prometeu-core/src/hardware/touch.rs @@ -1,5 +1,5 @@ -use crate::model::Button; use crate::hardware::input_signal::InputSignals; +use crate::model::Button; #[derive(Default, Clone, Copy, Debug)] pub struct Touch { diff --git a/crates/prometeu-core/src/lib.rs b/crates/prometeu-core/src/lib.rs index 87c706c6..2ffc9607 100644 --- a/crates/prometeu-core/src/lib.rs +++ b/crates/prometeu-core/src/lib.rs @@ -1,3 +1,20 @@ +//! # Prometeu Core +//! +//! This crate contains the engine and hardware definitions for the Prometeu Virtual Console. +//! It serves as the bridge between the physical host (desktop/web) and the virtualized +//! environment where games and apps run. +//! +//! ## Main Components: +//! - [`hardware`]: Virtual peripheral definitions (Graphics, Audio, Input). +//! - [`virtual_machine`]: The stack-based execution engine for Prometeu ByteCode. +//! - [`prometeu_os`]: Resource management, filesystem access, and syscall dispatching. +//! - [`firmware`]: System state machine (Boot, Hub, Game Loading, Crash Handler). +//! - [`model`]: Common data structures (Colors, Sprites, Cartridges). +//! - [`fs`]: Virtualized filesystem abstraction for sandboxed access. +//! - [`log`]: Centralized logging and telemetry service. +//! +//! The "Source of Truth" for the console's behavior lives here. + pub mod hardware; pub mod log; pub mod virtual_machine; diff --git a/crates/prometeu-core/src/log/log_service.rs b/crates/prometeu-core/src/log/log_service.rs index d3065683..a1b607e4 100644 --- a/crates/prometeu-core/src/log/log_service.rs +++ b/crates/prometeu-core/src/log/log_service.rs @@ -1,5 +1,5 @@ -use std::collections::VecDeque; use crate::log::{LogEvent, LogLevel, LogSource}; +use std::collections::VecDeque; pub struct LogService { events: VecDeque, diff --git a/crates/prometeu-core/src/log/mod.rs b/crates/prometeu-core/src/log/mod.rs index 547faf77..dd576c11 100644 --- a/crates/prometeu-core/src/log/mod.rs +++ b/crates/prometeu-core/src/log/mod.rs @@ -3,7 +3,7 @@ mod log_source; mod log_event; mod log_service; -pub use log_level::LogLevel; -pub use log_source::LogSource; pub use log_event::LogEvent; +pub use log_level::LogLevel; pub use log_service::LogService; +pub use log_source::LogSource; diff --git a/crates/prometeu-core/src/model/cartridge_loader.rs b/crates/prometeu-core/src/model/cartridge_loader.rs index b8fd024d..960e5f8c 100644 --- a/crates/prometeu-core/src/model/cartridge_loader.rs +++ b/crates/prometeu-core/src/model/cartridge_loader.rs @@ -1,6 +1,6 @@ +use crate::model::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; use std::fs; use std::path::Path; -use crate::model::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest}; pub struct CartridgeLoader; diff --git a/crates/prometeu-core/src/model/color.rs b/crates/prometeu-core/src/model/color.rs index 1cc09784..4e0fcffc 100644 --- a/crates/prometeu-core/src/model/color.rs +++ b/crates/prometeu-core/src/model/color.rs @@ -1,10 +1,13 @@ -/// Simple RGB565 color (0bRRRRRGGGGGGBBBBB). -/// - 5 bits Red -/// - 6 bits Green -/// - 5 bits Blue +/// Represents a 16-bit color in the RGB565 format. +/// +/// The RGB565 format is a common high-color representation for embedded systems: +/// - **Red**: 5 bits (0..31) +/// - **Green**: 6 bits (0..63) +/// - **Blue**: 5 bits (0..31) /// -/// There is no alpha channel. -/// Transparency is handled via Color Key or Blend Mode. +/// Prometeu does not have a hardware alpha channel. Transparency is achieved +/// by using a specific color key (Magenta / 0xF81F) which the GFX engine +/// skips during rendering. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Color(pub u16); diff --git a/crates/prometeu-core/src/model/mod.rs b/crates/prometeu-core/src/model/mod.rs index 3f7b7b61..ded41a56 100644 --- a/crates/prometeu-core/src/model/mod.rs +++ b/crates/prometeu-core/src/model/mod.rs @@ -11,7 +11,7 @@ mod cartridge; mod cartridge_loader; mod window; -pub use asset::{AssetEntry, BankType, BankStats, LoadStatus, SlotRef, SlotStats, HandleId, PreloadEntry}; +pub use asset::{AssetEntry, BankStats, BankType, HandleId, LoadStatus, PreloadEntry, SlotRef, SlotStats}; pub use button::{Button, ButtonId}; pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError}; pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader}; diff --git a/crates/prometeu-core/src/model/tile_layer.rs b/crates/prometeu-core/src/model/tile_layer.rs index f2153491..cbf975f5 100644 --- a/crates/prometeu-core/src/model/tile_layer.rs +++ b/crates/prometeu-core/src/model/tile_layer.rs @@ -1,5 +1,5 @@ -use crate::model::Tile; use crate::model::tile_bank::TileSize; +use crate::model::Tile; use crate::model::TileSize::Size8; pub struct TileMap { diff --git a/crates/prometeu-core/src/prometeu_hub/mod.rs b/crates/prometeu-core/src/prometeu_hub/mod.rs index 97e3702e..824717d0 100644 --- a/crates/prometeu-core/src/prometeu_hub/mod.rs +++ b/crates/prometeu-core/src/prometeu_hub/mod.rs @@ -1,3 +1,4 @@ mod prometeu_hub; +mod window_manager; pub use prometeu_hub::PrometeuHub; \ No newline at end of file diff --git a/crates/prometeu-core/src/prometeu_hub/prometeu_hub.rs b/crates/prometeu-core/src/prometeu_hub/prometeu_hub.rs index 311cf97b..d26fee0b 100644 --- a/crates/prometeu-core/src/prometeu_hub/prometeu_hub.rs +++ b/crates/prometeu-core/src/prometeu_hub/prometeu_hub.rs @@ -1,55 +1,9 @@ use crate::hardware::HardwareBridge; use crate::log::{LogLevel, LogSource}; -use crate::model::{Color, Rect, Window, WindowId}; +use crate::model::{Color, Rect}; +use crate::prometeu_hub::window_manager::WindowManager; use crate::prometeu_os::PrometeuOS; -/// PROMETEU Window Manager. -pub struct WindowManager { - pub windows: Vec, - pub focused: Option, -} - -impl WindowManager { - pub fn new() -> Self { - Self { - windows: Vec::new(), - focused: None, - } - } - - pub fn add_window(&mut self, title: String, viewport: Rect, color: Color) -> WindowId { - let id = WindowId(self.windows.len() as u32); - let window = Window { - id, - viewport, - has_focus: false, - title, - color, - }; - self.windows.push(window); - id - } - - pub fn remove_window(&mut self, id: WindowId) { - self.windows.retain(|w| w.id != id); - if self.focused == Some(id) { - self.focused = None; - } - } - - pub fn remove_all_windows(&mut self) { - self.windows.clear(); - self.focused = None; - } - - pub fn set_focus(&mut self, id: WindowId) { - self.focused = Some(id); - for window in &mut self.windows { - window.has_focus = window.id == id; - } - } -} - /// PrometeuHub: Launcher and system UI environment. pub struct PrometeuHub { pub window_manager: WindowManager, @@ -103,42 +57,4 @@ impl PrometeuHub { ); } } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::model::Rect; - - #[test] - fn test_window_manager_focus() { - let mut wm = WindowManager::new(); - let id1 = wm.add_window("Window 1".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); - let id2 = wm.add_window("Window 2".to_string(), Rect { x: 10, y: 10, w: 10, h: 10 }, Color::WHITE); - - assert_eq!(wm.windows.len(), 2); - assert_eq!(wm.focused, None); - - wm.set_focus(id1); - assert_eq!(wm.focused, Some(id1)); - assert!(wm.windows[0].has_focus); - assert!(!wm.windows[1].has_focus); - - wm.set_focus(id2); - assert_eq!(wm.focused, Some(id2)); - assert!(!wm.windows[0].has_focus); - assert!(wm.windows[1].has_focus); - } - - #[test] - fn test_window_manager_remove_window() { - let mut wm = WindowManager::new(); - let id = wm.add_window("Window".to_string(), Rect { x: 0, y: 0, w: 10, h: 10 }, Color::WHITE); - wm.set_focus(id); - assert_eq!(wm.focused, Some(id)); - - wm.remove_window(id); - assert_eq!(wm.windows.len(), 0); - assert_eq!(wm.focused, None); - } } \ No newline at end of file diff --git a/crates/prometeu-core/src/prometeu_os/mod.rs b/crates/prometeu-core/src/prometeu_os/mod.rs index 87fcf170..f19b1226 100644 --- a/crates/prometeu-core/src/prometeu_os/mod.rs +++ b/crates/prometeu-core/src/prometeu_os/mod.rs @@ -1,6 +1,4 @@ mod prometeu_os; -pub mod syscalls; +use crate::virtual_machine::NativeInterface; pub use prometeu_os::PrometeuOS; -pub use syscalls::Syscall; -pub use crate::virtual_machine::native_interface::NativeInterface; \ No newline at end of file diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index bddec8b2..1c283b78 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -1,62 +1,72 @@ use crate::fs::{FsBackend, FsState, VirtualFS}; +use crate::hardware::syscalls::Syscall; use crate::hardware::{HardwareBridge, InputSignals}; use crate::log::{LogLevel, LogService, LogSource}; use crate::model::{BankType, Cartridge, Color}; -use crate::prometeu_os::{NativeInterface, Syscall}; +use crate::prometeu_os::NativeInterface; use crate::telemetry::{CertificationConfig, Certifier, TelemetryFrame}; use crate::virtual_machine::{Value, VirtualMachine}; use std::collections::HashMap; use std::time::Instant; -/// PrometeuOS (POS): The system firmware/base. +/// PrometeuOS (POS): The central system firmware and resource manager. /// -/// Maximum authority for boot, peripherals, PVM execution, and fault handling. -/// It acts as the bridge between the high-level PVM applications and the virtual hardware. +/// POS is the "kernel" of the Prometeu console. It manages the lifecycle of +/// applications, handles hardware abstraction through Syscalls, and enforces +/// resource limits (CAP). +/// +/// ### Core Responsibilities: +/// - **Syscall Dispatching**: Implements the `NativeInterface` to connect the VM to hardware. +/// - **Resource Management**: Manages file handles, memory bank status, and cycle budgeting. +/// - **Filesystem Sandboxing**: Provides a virtualized filesystem (VFS) to the VM. +/// - **Telemetry & Certification**: Tracks performance and ensures apps stay within their budget. +/// - **Logging**: Provides a centralized log service for system and app events. pub struct PrometeuOS { - /// Incremented on every host tick (60Hz). + /// Host Tick Index: Incremented on every host hardware update (usually 60Hz). pub tick_index: u64, - /// Incremented every time a logical game frame is successfully completed. + /// Logical Frame Index: Incremented only when an app successfully completes a full frame via `FRAME_SYNC`. pub logical_frame_index: u64, - /// Indicates if there is an ongoing logical frame that hasn't reached `FRAME_SYNC` yet. + /// Execution State: True if the VM is currently mid-frame. pub logical_frame_active: bool, - /// Number of virtual cycles still available for the current logical frame. + /// Cycle Budget: The number of PVM cycles remaining for the current logical frame. pub logical_frame_remaining_cycles: u64, - /// Real-world CPU time (in microseconds) consumed by the last host tick. + /// Performance Metric: Time spent by the host CPU processing the last tick (in microseconds). pub last_frame_cpu_time_us: u64, - // Filesystem - /// The virtual filesystem interface. + // --- Filesystem --- + /// The virtual filesystem interface, abstracting the physical storage. pub fs: VirtualFS, - /// Current state of the FS (Mounted, Error, etc). + /// Current health and connection status of the virtual filesystem. pub fs_state: FsState, - /// Mapping of numeric handles to file paths for open files. + /// Active file handles mapping IDs to their virtual paths. pub open_files: HashMap, - /// Sequential handle generator for file operations. + /// Generator for unique, non-zero file handles. pub next_handle: u32, - // Log Service + // --- Logging & Identity --- + /// The centralized service for recording and retrieving system logs. pub log_service: LogService, - /// Unique ID of the currently running application. + /// Metadata for the currently running application. pub current_app_id: u32, pub current_cartridge_title: String, pub current_cartridge_app_version: String, pub current_cartridge_app_mode: crate::model::AppMode, - /// Rate-limiting tracker for application logs to prevent performance degradation. + /// Rate-limiter to prevent apps from flooding the log buffer and killing performance. pub logs_written_this_frame: HashMap, - // Telemetry and Certification - /// Accumulator for metrics of the frame currently being executed. + // --- Monitoring & Debugging --- + /// Running counters for the current execution slice. pub telemetry_current: TelemetryFrame, - /// Snapshot of the metrics from the last completed logical frame. + /// The results of the last successfully completed logical frame. pub telemetry_last: TelemetryFrame, - /// Logic for evaluating compliance with the active CAP profile. + /// Logic for validating that the app obeys the console's Certification (CAP). pub certifier: Certifier, - /// Global pause flag (used by debugger or system events). + /// Pause state: When true, `tick()` will not advance the VM. pub paused: bool, - /// Request from debugger to run exactly one instruction or frame. + /// Debugging flag to execute exactly one instruction or frame regardless of budget. pub debug_step_request: bool, - /// Instant when the system was initialized. + /// Wall-clock time of system startup. boot_time: Instant, } @@ -197,6 +207,7 @@ impl PrometeuOS { // Reset telemetry for the new logical frame self.telemetry_current = TelemetryFrame { frame_index: self.logical_frame_index, + cycles_budget: self.certifier.config.cycles_budget_per_frame.unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME), ..Default::default() }; } @@ -263,7 +274,7 @@ impl PrometeuOS { self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; - // Update bank telemetry in current frame (snapshot) + // Update bank telemetry in the current frame (snapshot) let gfx_stats = hw.assets().bank_info(BankType::TILES); self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes; self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_bytes; @@ -277,6 +288,7 @@ impl PrometeuOS { // If the frame ended exactly in this tick, we update the final real time in the latch. if !self.logical_frame_active && self.telemetry_last.frame_index == self.logical_frame_index.wrapping_sub(1) { self.telemetry_last.host_cpu_time_us = self.last_frame_cpu_time_us; + self.telemetry_last.cycles_budget = self.telemetry_current.cycles_budget; self.telemetry_last.gfx_used_bytes = self.telemetry_current.gfx_used_bytes; self.telemetry_last.gfx_inflight_bytes = self.telemetry_current.gfx_inflight_bytes; self.telemetry_last.gfx_slots_occupied = self.telemetry_current.gfx_slots_occupied; @@ -460,6 +472,27 @@ mod tests { 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 = PrometeuOS::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, PrometeuOS::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 = PrometeuOS::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 = PrometeuOS::new(None); diff --git a/crates/prometeu-core/src/telemetry.rs b/crates/prometeu-core/src/telemetry.rs index 1ede92e0..2dbe4f31 100644 --- a/crates/prometeu-core/src/telemetry.rs +++ b/crates/prometeu-core/src/telemetry.rs @@ -5,6 +5,7 @@ pub struct TelemetryFrame { pub frame_index: u64, pub vm_steps: u32, pub cycles_used: u64, + pub cycles_budget: u64, pub syscalls: u32, pub host_cpu_time_us: u64, pub violations: u32, diff --git a/crates/prometeu-core/src/virtual_machine/mod.rs b/crates/prometeu-core/src/virtual_machine/mod.rs index bc9a6a18..3a7fe6fc 100644 --- a/crates/prometeu-core/src/virtual_machine/mod.rs +++ b/crates/prometeu-core/src/virtual_machine/mod.rs @@ -3,10 +3,20 @@ mod value; mod call_frame; mod scope_frame; mod program; -pub mod native_interface; -pub use prometeu_bytecode::opcode::OpCode; +use crate::hardware::HardwareBridge; pub use program::Program; +pub use prometeu_bytecode::opcode::OpCode; pub use value::Value; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; +pub trait NativeInterface { + /// Dispatches a syscall from the Virtual Machine to the native implementation. + /// + /// ABI Rule: Arguments for the syscall are expected on the `operand_stack` in call order. + /// Since the stack is LIFO, the last argument of the call is the first to be popped. + /// + /// The implementation MUST pop all its arguments and SHOULD push a return value if the + /// syscall is defined to return one. + fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result; +} diff --git a/crates/prometeu-core/src/virtual_machine/native_interface.rs b/crates/prometeu-core/src/virtual_machine/native_interface.rs deleted file mode 100644 index bd7982c9..00000000 --- a/crates/prometeu-core/src/virtual_machine/native_interface.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::hardware::HardwareBridge; -use crate::virtual_machine::VirtualMachine; - -pub trait NativeInterface { - /// Dispatches a syscall from the Virtual Machine to the native implementation. - /// - /// ABI Rule: Arguments for the syscall are expected on the `operand_stack` in call order. - /// Since the stack is LIFO, the last argument of the call is the first to be popped. - /// - /// The implementation MUST pop all its arguments and SHOULD push a return value if the - /// syscall is defined to return one. - fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result; -} \ No newline at end of file diff --git a/crates/prometeu-core/src/virtual_machine/program.rs b/crates/prometeu-core/src/virtual_machine/program.rs index 616dd51b..e6a58592 100644 --- a/crates/prometeu-core/src/virtual_machine/program.rs +++ b/crates/prometeu-core/src/virtual_machine/program.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use crate::virtual_machine::Value; +use std::sync::Arc; #[derive(Debug, Clone, Default)] pub struct Program { diff --git a/crates/prometeu-core/src/virtual_machine/value.rs b/crates/prometeu-core/src/virtual_machine/value.rs index e1c00655..6e28e4cc 100644 --- a/crates/prometeu-core/src/virtual_machine/value.rs +++ b/crates/prometeu-core/src/virtual_machine/value.rs @@ -1,15 +1,28 @@ use serde::{Deserialize, Serialize}; use std::cmp::Ordering; +/// Represents any piece of data that can be stored on the VM stack or in globals. +/// +/// The PVM is "dynamically typed" at the bytecode level, meaning a single +/// `Value` enum can hold different primitive types. The VM performs +/// automatic type promotion (e.g., adding an Int32 to a Float64 results +/// in a Float64) to ensure mathematical correctness. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Value { + /// 32-bit signed integer. Used for most loop counters and indices. Int32(i32), + /// 64-bit signed integer. Used for large numbers and timestamps. Int64(i64), + /// 64-bit double precision float. Used for physics and complex math. Float(f64), + /// Boolean value (true/false). Boolean(bool), + /// UTF-8 string. Strings are immutable and usually come from the Constant Pool. String(String), - Ref(usize), // Heap reference + /// A pointer to an object on the heap. + Ref(usize), + /// Represents the absence of a value (equivalent to `null` or `undefined`). Null, } diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index e4926aa9..fb7bd039 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -1,9 +1,8 @@ use crate::hardware::HardwareBridge; -use crate::prometeu_os::NativeInterface; use crate::virtual_machine::call_frame::CallFrame; use crate::virtual_machine::scope_frame::ScopeFrame; use crate::virtual_machine::value::Value; -use crate::virtual_machine::Program; +use crate::virtual_machine::{NativeInterface, Program}; use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::pbc::{self, ConstantPoolEntry}; @@ -37,28 +36,41 @@ pub struct BudgetReport { /// The PVM (PROMETEU Virtual Machine). /// -/// A deterministic, stack-based virtual machine designed for educational purposes. -/// It models execution through fixed-cost cycles and explicit memory management. +/// A deterministic, stack-based virtual machine designed for game logic and +/// educational simulation. The PVM executes bytecode compiled from TypeScript/JS +/// and interacts with virtual hardware through a specialized instruction set. +/// +/// ### Architecture Highlights: +/// - **Stack-Based**: Most operations pop values from the stack and push results back. +/// - **Deterministic**: Execution is cycle-aware, allowing for precise performance budgeting. +/// - **Sandboxed**: No direct access to the host system; all I/O goes through Syscalls. +/// - **Type-Aware**: Supports integers, floats, booleans, and strings with automatic promotion. +/// +/// ### Memory Regions: +/// - **ROM**: Immutable instruction storage. +/// - **Operand Stack**: Fast-access temporary storage for calculations. +/// - **Global Pool**: Persistent storage for cross-frame variables. +/// - **Heap**: Dynamic memory for complex data (simplified version). pub struct VirtualMachine { - /// Program Counter: points to the next byte in the ROM to be executed. + /// Program Counter (PC): The absolute byte offset in ROM for the next instruction. pub pc: usize, - /// Operand Stack: used for intermediate calculations and passing arguments to opcodes. + /// Operand Stack: The primary workspace for all mathematical and logical operations. pub operand_stack: Vec, - /// Call Stack: stores execution frames for function calls. + /// Call Stack: Manages function call context (return addresses, frame limits). pub call_stack: Vec, - /// Scope Stack: stores frames for blocks within a function. + /// Scope Stack: Handles block-level local variable visibility (scopes). pub scope_stack: Vec, - /// Globals: storage for persistent variables that survive between frames. + /// Global Variable Store: Variables that persist for the lifetime of the program. pub globals: Vec, - /// The currently loaded program (Bytecode + Constant Pool). + /// The loaded executable (Bytecode + Constant Pool), that is the ROM translated. pub program: Program, - /// Dynamic memory region for complex structures (Simplified in current version). + /// Heap Memory: Dynamic allocation pool. pub heap: Vec, - /// Total accumulated execution cycles since the last reset. + /// Total virtual cycles consumed since the VM started. pub cycles: u64, - /// Flag indicating if the VM has been stopped by a `HALT` instruction. + /// Stop flag: true if a `HALT` opcode was encountered. pub halted: bool, - /// A set of PC addresses where execution should pause. + /// Set of ROM addresses used for software breakpoints in the debugger. pub breakpoints: std::collections::HashSet, } @@ -692,7 +704,6 @@ impl VirtualMachine { mod tests { use super::*; use crate::hardware::HardwareBridge; - use crate::prometeu_os::NativeInterface; use crate::virtual_machine::Value; struct MockNative; @@ -997,7 +1008,7 @@ mod tests { // Check if scope_stack was leaked (it currently would be if we don't clear it on RET) // The PR doesn't explicitly say RET should clear scope_stack, but it's good practice. - // "Não mexe em scopes intermediários (eles devem já ter sido fechados)" + // "Don't touch intermediate scopes (they should have already been closed)" // If they were closed, scope_stack would be empty for this frame. } diff --git a/crates/prometeu-runtime-desktop/src/cap.rs b/crates/prometeu-runtime-desktop/src/cap.rs index 511e9788..a0a09d4d 100644 --- a/crates/prometeu-runtime-desktop/src/cap.rs +++ b/crates/prometeu-runtime-desktop/src/cap.rs @@ -32,7 +32,7 @@ mod tests { #[test] fn test_load_cap_config() { - let content = "cycles_budget=500\nmax_syscalls=10\n# comentário\nmax_host_cpu_us=2000"; + let content = "cycles_budget=500\nmax_syscalls=10\n# comment\nmax_host_cpu_us=2000"; let path = "test_cap.cfg"; std::fs::write(path, content).unwrap(); diff --git a/crates/prometeu-runtime-desktop/src/debugger.rs b/crates/prometeu-runtime-desktop/src/debugger.rs index a4545e51..e5b52ab3 100644 --- a/crates/prometeu-runtime-desktop/src/debugger.rs +++ b/crates/prometeu-runtime-desktop/src/debugger.rs @@ -1,9 +1,9 @@ -use prometeu_core::firmware::{BootTarget, Firmware}; -use prometeu_core::Hardware; -use prometeu_core::model::CartridgeLoader; use prometeu_core::debugger_protocol::*; -use std::net::{TcpListener, TcpStream}; +use prometeu_core::firmware::{BootTarget, Firmware}; +use prometeu_core::model::CartridgeLoader; +use prometeu_core::Hardware; use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; /// Host-side implementation of the PROMETEU DevTools Protocol. /// @@ -276,6 +276,7 @@ impl HostDebugger { vm_steps: tel.vm_steps, syscalls: tel.syscalls, cycles: tel.cycles_used, + cycles_budget: tel.cycles_budget, host_cpu_time_us: tel.host_cpu_time_us, violations: tel.violations, gfx_used_bytes: tel.gfx_used_bytes, diff --git a/crates/prometeu-runtime-desktop/src/fs_backend.rs b/crates/prometeu-runtime-desktop/src/fs_backend.rs index 8a65e429..afb6f0f3 100644 --- a/crates/prometeu-runtime-desktop/src/fs_backend.rs +++ b/crates/prometeu-runtime-desktop/src/fs_backend.rs @@ -1,6 +1,6 @@ -use std::path::PathBuf; -use std::fs; use prometeu_core::fs::{FsBackend, FsEntry, FsError}; +use std::fs; +use std::path::PathBuf; pub struct HostDirBackend { root: PathBuf, @@ -85,8 +85,8 @@ impl FsBackend for HostDirBackend { #[cfg(test)] mod tests { use super::*; - use std::fs; use std::env; + use std::fs; fn get_temp_dir(name: &str) -> PathBuf { let mut path = env::temp_dir(); diff --git a/crates/prometeu-runtime-desktop/src/input.rs b/crates/prometeu-runtime-desktop/src/input.rs index f645c8a6..3632f0fe 100644 --- a/crates/prometeu-runtime-desktop/src/input.rs +++ b/crates/prometeu-runtime-desktop/src/input.rs @@ -1,8 +1,8 @@ +use prometeu_core::hardware::InputSignals; +use prometeu_core::Hardware; use winit::event::{ElementState, MouseButton, WindowEvent}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::Window; -use prometeu_core::hardware::InputSignals; -use prometeu_core::Hardware; pub struct HostInputHandler { pub signals: InputSignals, diff --git a/crates/prometeu-runtime-desktop/src/lib.rs b/crates/prometeu-runtime-desktop/src/lib.rs index 8922c35e..2bf68513 100644 --- a/crates/prometeu-runtime-desktop/src/lib.rs +++ b/crates/prometeu-runtime-desktop/src/lib.rs @@ -8,11 +8,11 @@ pub mod input; pub mod cap; pub mod utilities; -use runner::HostRunner; use cap::load_cap_config; -use winit::event_loop::EventLoop; -use prometeu_core::firmware::BootTarget; use clap::Parser; +use prometeu_core::firmware::BootTarget; +use runner::HostRunner; +use winit::event_loop::EventLoop; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] diff --git a/crates/prometeu-runtime-desktop/src/runner.rs b/crates/prometeu-runtime-desktop/src/runner.rs index afba80e1..03ded085 100644 --- a/crates/prometeu-runtime-desktop/src/runner.rs +++ b/crates/prometeu-runtime-desktop/src/runner.rs @@ -1,12 +1,12 @@ use crate::audio::HostAudio; -use crate::fs_backend::HostDirBackend; -use crate::log_sink::HostConsoleSink; use crate::debugger::HostDebugger; -use crate::stats::HostStats; +use crate::fs_backend::HostDirBackend; use crate::input::HostInputHandler; +use crate::log_sink::HostConsoleSink; +use crate::stats::HostStats; use crate::utilities::draw_rgb565_to_rgba8; -use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; use pixels::wgpu::PresentMode; +use pixels::{Pixels, PixelsBuilder, SurfaceTexture}; use prometeu_core::firmware::{BootTarget, Firmware}; use prometeu_core::Hardware; use std::time::{Duration, Instant}; @@ -128,12 +128,18 @@ impl HostRunner { let color_bg = prometeu_core::model::Color::INDIGO; // Dark blue to stand out let color_warn = prometeu_core::model::Color::RED; - self.hardware.gfx.fill_rect(5, 5, 140, 100, color_bg); + self.hardware.gfx.fill_rect(5, 5, 175, 100, color_bg); self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text); self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text); self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text); self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text); - self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text); + + let cycles_pct = if tel.cycles_budget > 0 { + (tel.cycles_used as f64 / tel.cycles_budget as f64) * 100.0 + } else { + 0.0 + }; + self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}/{} ({:.1}%)", tel.cycles_used, tel.cycles_budget, cycles_pct), color_text); self.hardware.gfx.draw_text(10, 50, &format!("GFX: {}K/16M ({}S)", tel.gfx_used_bytes / 1024, tel.gfx_slots_occupied), color_text); if tel.gfx_inflight_bytes > 0 { @@ -337,10 +343,10 @@ impl ApplicationHandler for HostRunner { #[cfg(test)] mod tests { use super::*; - use prometeu_core::firmware::BootTarget; use prometeu_core::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION; - use std::net::TcpStream; + use prometeu_core::firmware::BootTarget; use std::io::{Read, Write}; + use std::net::TcpStream; #[test] fn test_debug_port_opens() { diff --git a/devtools/debugger-protocol/protocol.json b/devtools/debugger-protocol/protocol.json index af022f0b..f92a1eee 100644 --- a/devtools/debugger-protocol/protocol.json +++ b/devtools/debugger-protocol/protocol.json @@ -52,7 +52,7 @@ { "event": "telemetry", "fields": [ - "frame_index", "vm_steps", "syscalls", "cycles", "host_cpu_time_us", "violations", + "frame_index", "vm_steps", "syscalls", "cycles", "cycles_budget", "host_cpu_time_us", "violations", "gfx_used_bytes", "gfx_inflight_bytes", "gfx_slots_occupied", "audio_used_bytes", "audio_inflight_bytes", "audio_slots_occupied" ]