clean up, debugger, add comments
This commit is contained in:
parent
28df72417b
commit
31080ea40d
@ -1,116 +1,116 @@
|
|||||||
# prometeu-bytecode
|
# 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
|
## 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
|
### Stack Notation Convention
|
||||||
Nas tabelas abaixo, usamos a seguinte notação para representar o estado da pilha:
|
In the tables below, we use the following notation to represent the state of the stack:
|
||||||
`[a, b] -> [c]`
|
`[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. |
|
| `Nop` | `0x00` | - | `[] -> []` | No operation. |
|
||||||
| `Halt` | `0x01` | - | `[] -> []` | Interrompe a execução da VM permanentemente. |
|
| `Halt` | `0x01` | - | `[] -> []` | Permanently halts VM execution. |
|
||||||
| `Jmp` | `0x02` | `addr: u32` | `[] -> []` | Salto incondicional para o endereço absoluto `addr`. |
|
| `Jmp` | `0x02` | `addr: u32` | `[] -> []` | Unconditional jump to absolute address `addr`. |
|
||||||
| `JmpIfFalse`| `0x03` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `false`. |
|
| `JmpIfFalse`| `0x03` | `addr: u32` | `[bool] -> []` | Jumps to `addr` if the popped value is `false`. |
|
||||||
| `JmpIfTrue` | `0x04` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `true`. |
|
| `JmpIfTrue` | `0x04` | `addr: u32` | `[bool] -> []` | Jumps to `addr` if the popped value is `true`. |
|
||||||
| `Trap` | `0x05` | - | `[] -> []` | Interrupção para debugger (breakpoint). |
|
| `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. |
|
| `PushConst` | `0x10` | `idx: u32` | `[] -> [val]` | Loads the constant at index `idx` from the Constant Pool. |
|
||||||
| `Pop` | `0x11` | - | `[val] -> []` | Remove e descarta o valor do topo da pilha. |
|
| `Pop` | `0x11` | - | `[val] -> []` | Removes and discards the top value of the stack. |
|
||||||
| `Dup` | `0x12` | - | `[val] -> [val, val]` | Duplica o valor no topo da pilha. |
|
| `Dup` | `0x12` | - | `[val] -> [val, val]` | Duplicates the value at the top of the stack. |
|
||||||
| `Swap` | `0x13` | - | `[a, b] -> [b, a]` | Inverte a posição dos dois valores no topo. |
|
| `Swap` | `0x13` | - | `[a, b] -> [b, a]` | Swaps the positions of the two values at the top. |
|
||||||
| `PushI64` | `0x14` | `val: i64` | `[] -> [i64]` | Empilha um inteiro de 64 bits imediato. |
|
| `PushI64` | `0x14` | `val: i64` | `[] -> [i64]` | Pushes an immediate 64-bit integer. |
|
||||||
| `PushF64` | `0x15` | `val: f64` | `[] -> [f64]` | Empilha um ponto flutuante de 64 bits imediato. |
|
| `PushF64` | `0x15` | `val: f64` | `[] -> [f64]` | Pushes an immediate 64-bit floating point. |
|
||||||
| `PushBool` | `0x16` | `val: u8` | `[] -> [bool]` | Empilha um booleano (0=false, 1=true). |
|
| `PushBool` | `0x16` | `val: u8` | `[] -> [bool]` | Pushes a boolean (0=false, 1=true). |
|
||||||
| `PushI32` | `0x17` | `val: i32` | `[] -> [i32]` | Empilha um inteiro de 32 bits imediato. |
|
| `PushI32` | `0x17` | `val: i32` | `[] -> [i32]` | Pushes an immediate 32-bit integer. |
|
||||||
| `PopN` | `0x18` | `n: u16` | `[...] -> [...]` | Remove `n` valores da pilha de uma vez. |
|
| `PopN` | `0x18` | `n: u16` | `[...] -> [...]` | Removes `n` values from the stack at once. |
|
||||||
|
|
||||||
### 6.3 Aritmética
|
### 6.3 Arithmetic
|
||||||
A VM realiza promoção automática de tipos (ex: `i32` + `f64` resulta em `f64`).
|
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. |
|
| `Add` | `0x20` | `[a, b] -> [a + b]` | Adds the two top values. |
|
||||||
| `Sub` | `0x21` | `[a, b] -> [a - b]` | Subtrai `b` de `a`. |
|
| `Sub` | `0x21` | `[a, b] -> [a - b]` | Subtracts `b` from `a`. |
|
||||||
| `Mul` | `0x22` | `[a, b] -> [a * b]` | Multiplica os dois valores do topo. |
|
| `Mul` | `0x22` | `[a, b] -> [a * b]` | Multiplies the two top values. |
|
||||||
| `Div` | `0x23` | `[a, b] -> [a / b]` | Divide `a` por `b`. Erro se `b == 0`. |
|
| `Div` | `0x23` | `[a, b] -> [a / b]` | Divides `a` by `b`. Error if `b == 0`. |
|
||||||
| `Neg` | `0x3E` | `[a] -> [-a]` | Inverte o sinal numérico. |
|
| `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. |
|
| `Eq` | `0x30` | `[a, b] -> [bool]` | Tests equality. |
|
||||||
| `Neq` | `0x31` | `[a, b] -> [bool]` | Testa desigualdade. |
|
| `Neq` | `0x31` | `[a, b] -> [bool]` | Tests inequality. |
|
||||||
| `Lt` | `0x32` | `[a, b] -> [bool]` | `a < b` |
|
| `Lt` | `0x32` | `[a, b] -> [bool]` | `a < b` |
|
||||||
| `Gt` | `0x33` | `[a, b] -> [bool]` | `a > b` |
|
| `Gt` | `0x33` | `[a, b] -> [bool]` | `a > b` |
|
||||||
| `Lte` | `0x3C` | `[a, b] -> [bool]` | `a <= b` |
|
| `Lte` | `0x3C` | `[a, b] -> [bool]` | `a <= b` |
|
||||||
| `Gte` | `0x3D` | `[a, b] -> [bool]` | `a >= b` |
|
| `Gte` | `0x3D` | `[a, b] -> [bool]` | `a >= b` |
|
||||||
| `And` | `0x34` | `[a, b] -> [bool]` | AND lógico (booleano). |
|
| `And` | `0x34` | `[a, b] -> [bool]` | Logical AND (boolean). |
|
||||||
| `Or` | `0x35` | `[a, b] -> [bool]` | OR lógico (booleano). |
|
| `Or` | `0x35` | `[a, b] -> [bool]` | Logical OR (boolean). |
|
||||||
| `Not` | `0x36` | `[a] -> [!a]` | NOT lógico. |
|
| `Not` | `0x36` | `[a] -> [!a]` | Logical NOT. |
|
||||||
| `BitAnd` | `0x37` | `[a, b] -> [int]` | AND bit a bit. |
|
| `BitAnd` | `0x37` | `[a, b] -> [int]` | Bitwise AND. |
|
||||||
| `BitOr` | `0x38` | `[a, b] -> [int]` | OR bit a bit. |
|
| `BitOr` | `0x38` | `[a, b] -> [int]` | Bitwise OR. |
|
||||||
| `BitXor` | `0x39` | `[a, b] -> [int]` | XOR bit a bit. |
|
| `BitXor` | `0x39` | `[a, b] -> [int]` | Bitwise XOR. |
|
||||||
| `Shl` | `0x3A` | `[a, b] -> [int]` | Shift Left: `a << b`. |
|
| `Shl` | `0x3A` | `[a, b] -> [int]` | Shift Left: `a << b`. |
|
||||||
| `Shr` | `0x3B` | `[a, b] -> [int]` | Shift Right: `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`. |
|
| `GetGlobal`| `0x40` | `idx: u32` | `[] -> [val]` | Loads value from global `idx`. |
|
||||||
| `SetGlobal`| `0x41` | `idx: u32` | `[val] -> []` | Armazena topo na global `idx`. |
|
| `SetGlobal`| `0x41` | `idx: u32` | `[val] -> []` | Stores top of stack in global `idx`. |
|
||||||
| `GetLocal` | `0x42` | `idx: u32` | `[] -> [val]` | Carrega local `idx` do frame atual. |
|
| `GetLocal` | `0x42` | `idx: u32` | `[] -> [val]` | Loads local `idx` from the current frame. |
|
||||||
| `SetLocal` | `0x43` | `idx: u32` | `[val] -> []` | Armazena topo na local `idx` do frame atual. |
|
| `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. |
|
| `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]` | Retorna da função atual, limpando o frame e devolvendo o valor do topo. |
|
| `Ret` | `0x51` | - | `[val] -> [val]` | Returns from the current function, clearing the frame and returning the top value. |
|
||||||
| `PushScope`| `0x52` | - | `[] -> []` | Inicia um sub-escopo (bloco) para locais temporários. |
|
| `PushScope`| `0x52` | - | `[] -> []` | Starts a sub-scope (block) for temporary locals. |
|
||||||
| `PopScope` | `0x53` | - | `[] -> []` | Finaliza sub-escopo, removendo locais criados nele da pilha. |
|
| `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. |
|
| `Alloc` | `0x60` | `size: u32` | `[] -> [ref]` | Allocates `size` slots on the heap and returns a reference. |
|
||||||
| `LoadRef` | `0x61` | `offset: u32`| `[ref] -> [val]` | Lê valor do heap no endereço `ref + offset`. |
|
| `LoadRef` | `0x61` | `offset: u32`| `[ref] -> [val]` | Reads value from the heap at address `ref + offset`. |
|
||||||
| `StoreRef`| `0x62` | `offset: u32`| `[ref, val] -> []` | Escreve `val` no heap no endereço `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. |
|
| `Syscall` | `0x70` | `id: u32` | `[...] -> [...]` | Invokes a system/firmware function. The stack depends on the syscall. |
|
||||||
| `FrameSync`| `0x80` | - | `[] -> []` | Marca o fim do processamento do frame lógico atual (60 FPS). |
|
| `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
|
```rust
|
||||||
// Exemplo de como carregar um arquivo PBC
|
// Example of how to load a PBC file
|
||||||
let bytes = std::fs::read("game.pbc")?;
|
let bytes = std::fs::read("game.pbc")?;
|
||||||
let pbc = prometeu_bytecode::pbc::parse_pbc(&bytes)?;
|
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
|
```rust
|
||||||
use prometeu_bytecode::asm::{assemble, Asm, Operand};
|
use prometeu_bytecode::asm::{assemble, Asm, Operand};
|
||||||
use prometeu_bytecode::opcode::OpCode;
|
use prometeu_bytecode::opcode::OpCode;
|
||||||
@ -139,7 +139,7 @@ let instructions = vec![
|
|||||||
let rom_bytes = assemble(&instructions).unwrap();
|
let rom_bytes = assemble(&instructions).unwrap();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Desmontagem (Disassembler)
|
### Disassembly (Disassembler)
|
||||||
```rust
|
```rust
|
||||||
use prometeu_bytecode::disasm::disasm;
|
use prometeu_bytecode::disasm::disasm;
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
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 {
|
pub fn operand_size(opcode: OpCode) -> usize {
|
||||||
match opcode {
|
match opcode {
|
||||||
OpCode::PushConst => 4,
|
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 {
|
pub fn is_jump(opcode: OpCode) -> bool {
|
||||||
match opcode {
|
match opcode {
|
||||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => true,
|
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 {
|
pub fn has_immediate(opcode: OpCode) -> bool {
|
||||||
operand_size(opcode) > 0
|
operand_size(opcode) > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,22 +2,33 @@ use crate::opcode::OpCode;
|
|||||||
use crate::readwrite::*;
|
use crate::readwrite::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Represents an operand for an instruction.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Operand {
|
pub enum Operand {
|
||||||
|
/// 32-bit unsigned integer (e.g., indices, addresses).
|
||||||
U32(u32),
|
U32(u32),
|
||||||
|
/// 32-bit signed integer.
|
||||||
I32(i32),
|
I32(i32),
|
||||||
|
/// 64-bit signed integer.
|
||||||
I64(i64),
|
I64(i64),
|
||||||
|
/// 64-bit floating point.
|
||||||
F64(f64),
|
F64(f64),
|
||||||
|
/// Boolean (true/false).
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
/// A symbolic label that will be resolved to an absolute PC address.
|
||||||
Label(String),
|
Label(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an assembly-level element (either an instruction or a label).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Asm {
|
pub enum Asm {
|
||||||
|
/// An OpCode followed by its operands. The mnemonics represent the operation to be performed.
|
||||||
Op(OpCode, Vec<Operand>),
|
Op(OpCode, Vec<Operand>),
|
||||||
|
/// A named marker in the code (e.g., "start:").
|
||||||
Label(String),
|
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<Operand>) -> u32 {
|
pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> u32 {
|
||||||
let mut pcp: u32 = initial_pc;
|
let mut pcp: u32 = initial_pc;
|
||||||
for operand in operands {
|
for operand in operands {
|
||||||
@ -30,6 +41,13 @@ pub fn update_pc_by_operand(initial_pc: u32, operands: &Vec<Operand>) -> u32 {
|
|||||||
pcp
|
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<Vec<u8>, String> {
|
pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
|
||||||
let mut labels = HashMap::new();
|
let mut labels = HashMap::new();
|
||||||
let mut current_pc = 0u32;
|
let mut current_pc = 0u32;
|
||||||
|
|||||||
@ -2,13 +2,18 @@ use crate::opcode::OpCode;
|
|||||||
use crate::readwrite::*;
|
use crate::readwrite::*;
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
|
/// Represents a disassembled instruction.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Instr {
|
pub struct Instr {
|
||||||
|
/// The absolute address (PC) where this instruction starts in ROM.
|
||||||
pub pc: u32,
|
pub pc: u32,
|
||||||
|
/// The decoded OpCode.
|
||||||
pub opcode: OpCode,
|
pub opcode: OpCode,
|
||||||
|
/// The list of operands extracted from the byte stream.
|
||||||
pub operands: Vec<DisasmOperand>,
|
pub operands: Vec<DisasmOperand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an operand decoded from the byte stream.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DisasmOperand {
|
pub enum DisasmOperand {
|
||||||
U32(u32),
|
U32(u32),
|
||||||
@ -18,6 +23,11 @@ pub enum DisasmOperand {
|
|||||||
Bool(bool),
|
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<Vec<Instr>, String> {
|
pub fn disasm(rom: &[u8]) -> Result<Vec<Instr>, String> {
|
||||||
let mut instructions = Vec::new();
|
let mut instructions = Vec::new();
|
||||||
let mut cursor = Cursor::new(rom);
|
let mut cursor = Cursor::new(rom);
|
||||||
|
|||||||
@ -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 opcode;
|
||||||
pub mod abi;
|
pub mod abi;
|
||||||
pub mod pbc;
|
pub mod pbc;
|
||||||
pub mod readwrite;
|
pub mod readwrite;
|
||||||
pub mod asm;
|
pub mod asm;
|
||||||
pub mod disasm;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum OpCode {
|
pub enum OpCode {
|
||||||
// 6.1 Execution Control
|
// --- 6.1 Execution Control ---
|
||||||
|
|
||||||
|
/// No operation. Does nothing for 1 cycle.
|
||||||
Nop = 0x00,
|
Nop = 0x00,
|
||||||
|
/// Stops the Virtual Machine execution immediately.
|
||||||
Halt = 0x01,
|
Halt = 0x01,
|
||||||
|
/// Unconditional jump to a specific PC (Program Counter) address.
|
||||||
|
/// Operand: addr (u32)
|
||||||
Jmp = 0x02,
|
Jmp = 0x02,
|
||||||
|
/// Jumps to `addr` if the value at the top of the stack is `false`.
|
||||||
|
/// Operand: addr (u32)
|
||||||
|
/// Stack: [bool] -> []
|
||||||
JmpIfFalse = 0x03,
|
JmpIfFalse = 0x03,
|
||||||
|
/// Jumps to `addr` if the value at the top of the stack is `true`.
|
||||||
|
/// Operand: addr (u32)
|
||||||
|
/// Stack: [bool] -> []
|
||||||
JmpIfTrue = 0x04,
|
JmpIfTrue = 0x04,
|
||||||
|
/// Triggers a software breakpoint. Used for debugging.
|
||||||
Trap = 0x05,
|
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,
|
PushConst = 0x10,
|
||||||
|
/// Removes the top value from the stack.
|
||||||
|
/// Stack: [val] -> []
|
||||||
Pop = 0x11,
|
Pop = 0x11,
|
||||||
|
/// Duplicates the top value of the stack.
|
||||||
|
/// Stack: [val] -> [val, val]
|
||||||
Dup = 0x12,
|
Dup = 0x12,
|
||||||
|
/// Swaps the two top values of the stack.
|
||||||
|
/// Stack: [a, b] -> [b, a]
|
||||||
Swap = 0x13,
|
Swap = 0x13,
|
||||||
|
/// Pushes a 64-bit integer literal onto the stack.
|
||||||
|
/// Operand: value (i64)
|
||||||
PushI64 = 0x14,
|
PushI64 = 0x14,
|
||||||
|
/// Pushes a 64-bit float literal onto the stack.
|
||||||
|
/// Operand: value (f64)
|
||||||
PushF64 = 0x15,
|
PushF64 = 0x15,
|
||||||
|
/// Pushes a boolean literal onto the stack (0=false, 1=true).
|
||||||
|
/// Operand: value (u8)
|
||||||
PushBool = 0x16,
|
PushBool = 0x16,
|
||||||
|
/// Pushes a 32-bit integer literal onto the stack.
|
||||||
|
/// Operand: value (i32)
|
||||||
PushI32 = 0x17,
|
PushI32 = 0x17,
|
||||||
|
/// Removes `n` values from the stack.
|
||||||
|
/// Operand: n (u16)
|
||||||
PopN = 0x18,
|
PopN = 0x18,
|
||||||
|
|
||||||
// 6.3 Arithmetic
|
// --- 6.3 Arithmetic ---
|
||||||
|
|
||||||
|
/// Adds the two top values (a + b).
|
||||||
|
/// Stack: [a, b] -> [result]
|
||||||
Add = 0x20,
|
Add = 0x20,
|
||||||
|
/// Subtracts the top value from the second one (a - b).
|
||||||
|
/// Stack: [a, b] -> [result]
|
||||||
Sub = 0x21,
|
Sub = 0x21,
|
||||||
|
/// Multiplies the two top values (a * b).
|
||||||
|
/// Stack: [a, b] -> [result]
|
||||||
Mul = 0x22,
|
Mul = 0x22,
|
||||||
|
/// Divides the second top value by the top one (a / b).
|
||||||
|
/// Stack: [a, b] -> [result]
|
||||||
Div = 0x23,
|
Div = 0x23,
|
||||||
|
|
||||||
// 6.4 Comparison and Logic
|
// --- 6.4 Comparison and Logic ---
|
||||||
|
|
||||||
|
/// Checks if a equals b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Eq = 0x30,
|
Eq = 0x30,
|
||||||
|
/// Checks if a is not equal to b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Neq = 0x31,
|
Neq = 0x31,
|
||||||
|
/// Checks if a is less than b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Lt = 0x32,
|
Lt = 0x32,
|
||||||
|
/// Checks if a is greater than b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Gt = 0x33,
|
Gt = 0x33,
|
||||||
|
/// Logical AND.
|
||||||
|
/// Stack: [bool, bool] -> [bool]
|
||||||
And = 0x34,
|
And = 0x34,
|
||||||
|
/// Logical OR.
|
||||||
|
/// Stack: [bool, bool] -> [bool]
|
||||||
Or = 0x35,
|
Or = 0x35,
|
||||||
|
/// Logical NOT.
|
||||||
|
/// Stack: [bool] -> [bool]
|
||||||
Not = 0x36,
|
Not = 0x36,
|
||||||
|
/// Bitwise AND.
|
||||||
|
/// Stack: [int, int] -> [int]
|
||||||
BitAnd = 0x37,
|
BitAnd = 0x37,
|
||||||
|
/// Bitwise OR.
|
||||||
|
/// Stack: [int, int] -> [int]
|
||||||
BitOr = 0x38,
|
BitOr = 0x38,
|
||||||
|
/// Bitwise XOR.
|
||||||
|
/// Stack: [int, int] -> [int]
|
||||||
BitXor = 0x39,
|
BitXor = 0x39,
|
||||||
|
/// Bitwise Shift Left.
|
||||||
|
/// Stack: [int, count] -> [int]
|
||||||
Shl = 0x3A,
|
Shl = 0x3A,
|
||||||
|
/// Bitwise Shift Right.
|
||||||
|
/// Stack: [int, count] -> [int]
|
||||||
Shr = 0x3B,
|
Shr = 0x3B,
|
||||||
|
/// Checks if a is less than or equal to b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Lte = 0x3C,
|
Lte = 0x3C,
|
||||||
|
/// Checks if a is greater than or equal to b.
|
||||||
|
/// Stack: [a, b] -> [bool]
|
||||||
Gte = 0x3D,
|
Gte = 0x3D,
|
||||||
|
/// Negates a number (-a).
|
||||||
|
/// Stack: [num] -> [num]
|
||||||
Neg = 0x3E,
|
Neg = 0x3E,
|
||||||
|
|
||||||
// 6.5 Variables
|
// --- 6.5 Variables ---
|
||||||
|
|
||||||
|
/// Loads a value from a global variable slot.
|
||||||
|
/// Operand: slot_index (u32)
|
||||||
|
/// Stack: [] -> [value]
|
||||||
GetGlobal = 0x40,
|
GetGlobal = 0x40,
|
||||||
|
/// Stores the top value into a global variable slot.
|
||||||
|
/// Operand: slot_index (u32)
|
||||||
|
/// Stack: [value] -> []
|
||||||
SetGlobal = 0x41,
|
SetGlobal = 0x41,
|
||||||
|
/// Loads a value from a local variable slot in the current frame.
|
||||||
|
/// Operand: slot_index (u32)
|
||||||
|
/// Stack: [] -> [value]
|
||||||
GetLocal = 0x42,
|
GetLocal = 0x42,
|
||||||
|
/// Stores the top value into a local variable slot in the current frame.
|
||||||
|
/// Operand: slot_index (u32)
|
||||||
|
/// Stack: [value] -> []
|
||||||
SetLocal = 0x43,
|
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,
|
Call = 0x50,
|
||||||
|
/// Returns from the current function.
|
||||||
|
/// Stack: [return_val] -> [return_val]
|
||||||
Ret = 0x51,
|
Ret = 0x51,
|
||||||
|
/// Starts a new local scope (for blocks/loops).
|
||||||
PushScope = 0x52,
|
PushScope = 0x52,
|
||||||
|
/// Ends the current local scope, discarding its local variables.
|
||||||
PopScope = 0x53,
|
PopScope = 0x53,
|
||||||
|
|
||||||
// 6.7 Heap
|
// --- 6.7 Heap ---
|
||||||
|
|
||||||
|
/// Allocates `size` slots on the heap.
|
||||||
|
/// Stack: [size] -> [reference]
|
||||||
Alloc = 0x60,
|
Alloc = 0x60,
|
||||||
|
/// Reads a value from the heap at `reference + offset`.
|
||||||
|
/// Operand: offset (u32)
|
||||||
|
/// Stack: [reference] -> [value]
|
||||||
LoadRef = 0x61,
|
LoadRef = 0x61,
|
||||||
|
/// Writes a value to the heap at `reference + offset`.
|
||||||
|
/// Operand: offset (u32)
|
||||||
|
/// Stack: [reference, value] -> []
|
||||||
StoreRef = 0x62,
|
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,
|
Syscall = 0x70,
|
||||||
|
/// Synchronizes the VM with the hardware frame (usually 60Hz).
|
||||||
|
/// Execution pauses until the next VSync.
|
||||||
FrameSync = 0x80,
|
FrameSync = 0x80,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +236,8 @@ impl TryFrom<u16> for OpCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn cycles(&self) -> u64 {
|
||||||
match self {
|
match self {
|
||||||
OpCode::Nop => 1,
|
OpCode::Nop => 1,
|
||||||
|
|||||||
@ -1,22 +1,47 @@
|
|||||||
use crate::readwrite::*;
|
use crate::readwrite::*;
|
||||||
use std::io::{Cursor, Read, Write};
|
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 <index>` to load these values onto the stack.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ConstantPoolEntry {
|
pub enum ConstantPoolEntry {
|
||||||
|
/// Reserved index (0). Represents a null/undefined value.
|
||||||
Null,
|
Null,
|
||||||
|
/// A 64-bit integer constant.
|
||||||
Int64(i64),
|
Int64(i64),
|
||||||
|
/// A 64-bit floating point constant.
|
||||||
Float64(f64),
|
Float64(f64),
|
||||||
|
/// A boolean constant.
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
|
/// A UTF-8 string constant.
|
||||||
String(String),
|
String(String),
|
||||||
|
/// A 32-bit integer constant.
|
||||||
Int32(i32),
|
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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct PbcFile {
|
pub struct PbcFile {
|
||||||
|
/// The list of constants used by the program.
|
||||||
pub cp: Vec<ConstantPoolEntry>,
|
pub cp: Vec<ConstantPoolEntry>,
|
||||||
|
/// The raw instruction bytes (ROM).
|
||||||
pub rom: Vec<u8>,
|
pub rom: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<PbcFile, String> {
|
pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
||||||
if bytes.len() < 4 || &bytes[0..4] != b"PPBC" {
|
if bytes.len() < 4 || &bytes[0..4] != b"PPBC" {
|
||||||
return Err("Invalid PBC signature".into());
|
return Err("Invalid PBC signature".into());
|
||||||
@ -68,6 +93,9 @@ pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
|||||||
Ok(PbcFile { cp, rom })
|
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<Vec<u8>, String> {
|
pub fn write_pbc(pbc: &PbcFile) -> Result<Vec<u8>, String> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
out.write_all(b"PPBC").map_err(|e| e.to_string())?;
|
out.write_all(b"PPBC").map_err(|e| e.to_string())?;
|
||||||
@ -109,3 +137,48 @@ pub fn write_pbc(pbc: &PbcFile) -> Result<Vec<u8>, String> {
|
|||||||
|
|
||||||
Ok(out)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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};
|
use std::io::{self, Read, Write};
|
||||||
|
|
||||||
|
/// Reads a 16-bit unsigned integer from a reader.
|
||||||
pub fn read_u16_le<R: Read>(mut reader: R) -> io::Result<u16> {
|
pub fn read_u16_le<R: Read>(mut reader: R) -> io::Result<u16> {
|
||||||
let mut buf = [0u8; 2];
|
let mut buf = [0u8; 2];
|
||||||
reader.read_exact(&mut buf)?;
|
reader.read_exact(&mut buf)?;
|
||||||
Ok(u16::from_le_bytes(buf))
|
Ok(u16::from_le_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a 32-bit unsigned integer from a reader.
|
||||||
pub fn read_u32_le<R: Read>(mut reader: R) -> io::Result<u32> {
|
pub fn read_u32_le<R: Read>(mut reader: R) -> io::Result<u32> {
|
||||||
let mut buf = [0u8; 4];
|
let mut buf = [0u8; 4];
|
||||||
reader.read_exact(&mut buf)?;
|
reader.read_exact(&mut buf)?;
|
||||||
Ok(u32::from_le_bytes(buf))
|
Ok(u32::from_le_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a 64-bit signed integer from a reader.
|
||||||
pub fn read_i64_le<R: Read>(mut reader: R) -> io::Result<i64> {
|
pub fn read_i64_le<R: Read>(mut reader: R) -> io::Result<i64> {
|
||||||
let mut buf = [0u8; 8];
|
let mut buf = [0u8; 8];
|
||||||
reader.read_exact(&mut buf)?;
|
reader.read_exact(&mut buf)?;
|
||||||
Ok(i64::from_le_bytes(buf))
|
Ok(i64::from_le_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a 64-bit floating point from a reader.
|
||||||
pub fn read_f64_le<R: Read>(mut reader: R) -> io::Result<f64> {
|
pub fn read_f64_le<R: Read>(mut reader: R) -> io::Result<f64> {
|
||||||
let mut buf = [0u8; 8];
|
let mut buf = [0u8; 8];
|
||||||
reader.read_exact(&mut buf)?;
|
reader.read_exact(&mut buf)?;
|
||||||
Ok(f64::from_le_bytes(buf))
|
Ok(f64::from_le_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a 16-bit unsigned integer to a writer.
|
||||||
pub fn write_u16_le<W: Write>(mut writer: W, val: u16) -> io::Result<()> {
|
pub fn write_u16_le<W: Write>(mut writer: W, val: u16) -> io::Result<()> {
|
||||||
writer.write_all(&val.to_le_bytes())
|
writer.write_all(&val.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a 32-bit unsigned integer to a writer.
|
||||||
pub fn write_u32_le<W: Write>(mut writer: W, val: u32) -> io::Result<()> {
|
pub fn write_u32_le<W: Write>(mut writer: W, val: u32) -> io::Result<()> {
|
||||||
writer.write_all(&val.to_le_bytes())
|
writer.write_all(&val.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a 64-bit signed integer to a writer.
|
||||||
pub fn write_i64_le<W: Write>(mut writer: W, val: i64) -> io::Result<()> {
|
pub fn write_i64_le<W: Write>(mut writer: W, val: i64) -> io::Result<()> {
|
||||||
writer.write_all(&val.to_le_bytes())
|
writer.write_all(&val.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes a 64-bit floating point to a writer.
|
||||||
pub fn write_f64_le<W: Write>(mut writer: W, val: f64) -> io::Result<()> {
|
pub fn write_f64_le<W: Write>(mut writer: W, val: f64) -> io::Result<()> {
|
||||||
writer.write_all(&val.to_le_bytes())
|
writer.write_all(&val.to_le_bytes())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<PathBuf>,
|
|
||||||
|
|
||||||
/// Output PBC file
|
|
||||||
#[arg(short, long)]
|
|
||||||
out: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,15 +1,23 @@
|
|||||||
|
use anyhow::{anyhow, Result};
|
||||||
use oxc_ast::ast::*;
|
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<String> {
|
pub fn get_callee_name(expr: &Expression) -> Result<String> {
|
||||||
get_member_expr_name(expr)
|
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<String> {
|
pub fn get_member_expr_name(expr: &Expression) -> Result<String> {
|
||||||
match expr {
|
match expr {
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
let name = ident.name.to_string();
|
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() {
|
if name.len() > 1 && name.starts_with('P') && name.chars().nth(1).unwrap().is_uppercase() {
|
||||||
Ok(name[1..].to_string())
|
Ok(name[1..].to_string())
|
||||||
} else {
|
} else {
|
||||||
@ -21,6 +29,6 @@ pub fn get_member_expr_name(expr: &Expression) -> Result<String> {
|
|||||||
let prop = member.property.name.to_string();
|
let prop = member.property.name.to_string();
|
||||||
Ok(format!("{}.{}", obj, prop))
|
Ok(format!("{}.{}", obj, prop))
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("Unsupported expression")),
|
_ => Err(anyhow!("Unsupported expression for name resolution")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 crate::codegen::ast_util;
|
||||||
use prometeu_core::prometeu_os::Syscall;
|
use crate::codegen::{input_map, syscall_map};
|
||||||
use std::collections::HashMap;
|
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;
|
||||||
|
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 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 {
|
pub struct Codegen {
|
||||||
|
/// Name of the file being compiled (used for debug symbols).
|
||||||
file_name: String,
|
file_name: String,
|
||||||
|
/// Full source code of the file (used for position lookup).
|
||||||
source_text: String,
|
source_text: String,
|
||||||
|
/// Collected debug symbols mapping PC to source lines.
|
||||||
pub symbols: Vec<Symbol>,
|
pub symbols: Vec<Symbol>,
|
||||||
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<String, u32>,
|
locals: HashMap<String, u32>,
|
||||||
|
/// Mapping of global variable names to their slots in the VM's global memory.
|
||||||
globals: HashMap<String, u32>,
|
globals: HashMap<String, u32>,
|
||||||
|
/// The Constant Pool, which stores unique values (strings, large numbers).
|
||||||
constant_pool: Vec<ConstantPoolEntry>,
|
constant_pool: Vec<ConstantPoolEntry>,
|
||||||
|
/// Counter for the next available local variable ID.
|
||||||
next_local: u32,
|
next_local: u32,
|
||||||
|
/// Counter for the next available global variable ID.
|
||||||
next_global: u32,
|
next_global: u32,
|
||||||
|
/// Counter for generating unique labels (e.g., for 'if' or 'while' blocks).
|
||||||
label_count: u32,
|
label_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Codegen {
|
impl Codegen {
|
||||||
|
/// Creates a new Codegen instance for a specific file.
|
||||||
pub fn new(file_name: String, source_text: String) -> Self {
|
pub fn new(file_name: String, source_text: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
file_name,
|
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 {
|
fn add_constant(&mut self, entry: ConstantPoolEntry) -> u32 {
|
||||||
if let Some(pos) = self.constant_pool.iter().position(|e| e == &entry) {
|
if let Some(pos) = self.constant_pool.iter().position(|e| e == &entry) {
|
||||||
pos as u32
|
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<Vec<u8>> {
|
pub fn compile_program(&mut self, program: &Program) -> Result<Vec<u8>> {
|
||||||
self.compile_programs(vec![(self.file_name.clone(), self.source_text.clone(), program)])
|
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<Vec<u8>> {
|
pub fn compile_programs(&mut self, programs: Vec<(String, String, &Program)>) -> Result<Vec<u8>> {
|
||||||
// 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();
|
let mut all_functions = Vec::new();
|
||||||
for (file, source, program) in &programs {
|
for (file, source, program) in &programs {
|
||||||
for item in &program.body {
|
for item in &program.body {
|
||||||
match item {
|
match item {
|
||||||
|
// Standard function declaration: function foo() { ... }
|
||||||
Statement::FunctionDeclaration(f) => {
|
Statement::FunctionDeclaration(f) => {
|
||||||
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
||||||
}
|
}
|
||||||
|
// Exported declaration: export function foo() ... or export const x = 1;
|
||||||
Statement::ExportNamedDeclaration(decl) => {
|
Statement::ExportNamedDeclaration(decl) => {
|
||||||
if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration {
|
if let Some(Declaration::FunctionDeclaration(f)) = &decl.declaration {
|
||||||
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
all_functions.push((file.clone(), source.clone(), f.as_ref()));
|
||||||
} else if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
|
} else if let Some(Declaration::VariableDeclaration(var)) = &decl.declaration {
|
||||||
for decl in &var.declarations {
|
self.export_global_variable_declarations(&var);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Global variable declaration: var x = 1;
|
||||||
Statement::VariableDeclaration(var) => {
|
Statement::VariableDeclaration(var) => {
|
||||||
for decl in &var.declarations {
|
self.export_global_variable_declarations(&var);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
let mut frame_fn_name = None;
|
||||||
if let Some((_, _, entry_program)) = programs.first() {
|
if let Some((_, _, entry_program)) = programs.first() {
|
||||||
for item in &entry_program.body {
|
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"))?;
|
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 {
|
for (file, source, program) in &programs {
|
||||||
self.file_name = file.clone();
|
self.file_name = file.clone();
|
||||||
self.source_text = source.clone();
|
self.source_text = source.clone();
|
||||||
@ -149,10 +170,13 @@ impl Codegen {
|
|||||||
let name = ident.name.to_string();
|
let name = ident.name.to_string();
|
||||||
let id = *self.globals.get(&name).unwrap();
|
let id = *self.globals.get(&name).unwrap();
|
||||||
if let Some(init) = &decl.init {
|
if let Some(init) = &decl.init {
|
||||||
|
// Compiles the initialization expression (e.g., the "10" in "var x = 10")
|
||||||
self.compile_expr(init)?;
|
self.compile_expr(init)?;
|
||||||
} else {
|
} else {
|
||||||
|
// If there is no initializer, it defaults to 0 (I32)
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span);
|
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);
|
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_label("entry".to_string());
|
||||||
self.emit_op(OpCode::Call, vec![Operand::Label(frame_fn_name), Operand::U32(0)], Span::default());
|
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::Pop, vec![], Span::default());
|
||||||
self.emit_op(OpCode::FrameSync, vec![], Span::default());
|
self.emit_op(OpCode::FrameSync, vec![], Span::default());
|
||||||
self.emit_op(OpCode::Jmp, vec![Operand::Label("entry".to_string())], 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 {
|
for (file, source, f) in all_functions {
|
||||||
self.file_name = file;
|
self.file_name = file;
|
||||||
self.source_text = source;
|
self.source_text = source;
|
||||||
if let Some(ident) = &f.id {
|
if let Some(ident) = &f.id {
|
||||||
let name = ident.name.to_string();
|
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.emit_label(name);
|
||||||
self.compile_function(f)?;
|
self.compile_function(f)?;
|
||||||
|
// Ensures the function returns to the caller
|
||||||
self.emit_op(OpCode::Ret, vec![], Span::default());
|
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<Asm> = self.instructions.iter().map(|(a, _)| a.clone()).collect();
|
let asm_vec: Vec<Asm> = self.instructions.iter().map(|(a, _)| a.clone()).collect();
|
||||||
let bytecode = assemble(&asm_vec).map_err(|e| anyhow!("Assemble error: {}", e))?;
|
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();
|
self.finalize_symbols();
|
||||||
|
|
||||||
|
// 3. Builds the PBC file structure (Constant Pool + Bytecode)
|
||||||
let pbc = PbcFile {
|
let pbc = PbcFile {
|
||||||
cp: self.constant_pool.clone(),
|
cp: self.constant_pool.clone(),
|
||||||
rom: bytecode,
|
rom: bytecode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 4. Serializes to the final binary format
|
||||||
write_pbc(&pbc).map_err(|e| anyhow!("PBC Write error: {}", e))
|
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<()> {
|
fn compile_function(&mut self, f: &Function) -> Result<()> {
|
||||||
self.locals.clear();
|
self.locals.clear();
|
||||||
self.next_local = 0;
|
self.next_local = 0;
|
||||||
@ -200,7 +259,7 @@ impl Codegen {
|
|||||||
// Start scope for parameters and local variables
|
// Start scope for parameters and local variables
|
||||||
self.emit_op(OpCode::PushScope, vec![], f.span);
|
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 {
|
for param in &f.params.items {
|
||||||
if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern {
|
if let BindingPattern::BindingIdentifier(ident) = ¶m.pattern {
|
||||||
let name = ident.name.to_string();
|
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.
|
// 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.
|
// 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::PopScope, vec![], Span::default());
|
||||||
self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], Span::default()); // Index 0 is Null in PBC
|
self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], Span::default()); // Index 0 is Null in PBC
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Translates a Statement into bytecode.
|
||||||
fn compile_stmt(&mut self, stmt: &Statement) -> Result<()> {
|
fn compile_stmt(&mut self, stmt: &Statement) -> Result<()> {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
// var x = 10;
|
||||||
Statement::VariableDeclaration(var) => {
|
Statement::VariableDeclaration(var) => {
|
||||||
for decl in &var.declarations {
|
for decl in &var.declarations {
|
||||||
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
|
if let BindingPattern::BindingIdentifier(ident) = &decl.id {
|
||||||
@ -234,6 +293,7 @@ impl Codegen {
|
|||||||
if let Some(init) = &decl.init {
|
if let Some(init) = &decl.init {
|
||||||
self.compile_expr(init)?;
|
self.compile_expr(init)?;
|
||||||
} else {
|
} else {
|
||||||
|
// Default initialization to 0
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span);
|
self.emit_op(OpCode::PushI32, vec![Operand::I32(0)], decl.span);
|
||||||
}
|
}
|
||||||
let id = self.next_local;
|
let id = self.next_local;
|
||||||
@ -242,10 +302,13 @@ impl Codegen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// console.log("hello");
|
||||||
Statement::ExpressionStatement(expr_stmt) => {
|
Statement::ExpressionStatement(expr_stmt) => {
|
||||||
self.compile_expr(&expr_stmt.expression)?;
|
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);
|
self.emit_op(OpCode::Pop, vec![], expr_stmt.span);
|
||||||
}
|
}
|
||||||
|
// if (a == b) { ... } else { ... }
|
||||||
Statement::IfStatement(if_stmt) => {
|
Statement::IfStatement(if_stmt) => {
|
||||||
let else_label = self.new_label("else");
|
let else_label = self.new_label("else");
|
||||||
let end_label = self.new_label("end_if");
|
let end_label = self.new_label("end_if");
|
||||||
@ -263,6 +326,7 @@ impl Codegen {
|
|||||||
|
|
||||||
self.emit_label(end_label);
|
self.emit_label(end_label);
|
||||||
}
|
}
|
||||||
|
// { let x = 1; }
|
||||||
Statement::BlockStatement(block) => {
|
Statement::BlockStatement(block) => {
|
||||||
self.emit_op(OpCode::PushScope, vec![], block.span);
|
self.emit_op(OpCode::PushScope, vec![], block.span);
|
||||||
for stmt in &block.body {
|
for stmt in &block.body {
|
||||||
@ -275,8 +339,11 @@ impl Codegen {
|
|||||||
Ok(())
|
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<()> {
|
fn compile_expr(&mut self, expr: &Expression) -> Result<()> {
|
||||||
match expr {
|
match expr {
|
||||||
|
// Literals: push the value directly onto the stack
|
||||||
Expression::NumericLiteral(n) => {
|
Expression::NumericLiteral(n) => {
|
||||||
let val = n.value;
|
let val = n.value;
|
||||||
if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 {
|
if val.fract() == 0.0 && val >= i32::MIN as f64 && val <= i32::MAX as f64 {
|
||||||
@ -295,6 +362,7 @@ impl Codegen {
|
|||||||
Expression::NullLiteral(n) => {
|
Expression::NullLiteral(n) => {
|
||||||
self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], n.span);
|
self.emit_op(OpCode::PushConst, vec![Operand::U32(0)], n.span);
|
||||||
}
|
}
|
||||||
|
// Variable access: resolve to GetLocal or GetGlobal
|
||||||
Expression::Identifier(ident) => {
|
Expression::Identifier(ident) => {
|
||||||
let name = ident.name.to_string();
|
let name = ident.name.to_string();
|
||||||
if let Some(&id) = self.locals.get(&name) {
|
if let Some(&id) = self.locals.get(&name) {
|
||||||
@ -305,13 +373,14 @@ impl Codegen {
|
|||||||
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
return Err(anyhow!("Undefined variable: {} at {:?}", name, ident.span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Assignment: evaluate RHS and store result in LHS slot
|
||||||
Expression::AssignmentExpression(assign) => {
|
Expression::AssignmentExpression(assign) => {
|
||||||
if let AssignmentTarget::AssignmentTargetIdentifier(ident) = &assign.left {
|
if let AssignmentTarget::AssignmentTargetIdentifier(ident) = &assign.left {
|
||||||
let name = ident.name.to_string();
|
let name = ident.name.to_string();
|
||||||
self.compile_expr(&assign.right)?;
|
self.compile_expr(&assign.right)?;
|
||||||
if let Some(&id) = self.locals.get(&name) {
|
if let Some(&id) = self.locals.get(&name) {
|
||||||
self.emit_op(OpCode::SetLocal, vec![Operand::U32(id)], assign.span);
|
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) {
|
} else if let Some(&id) = self.globals.get(&name) {
|
||||||
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], assign.span);
|
self.emit_op(OpCode::SetGlobal, vec![Operand::U32(id)], assign.span);
|
||||||
self.emit_op(OpCode::GetGlobal, 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));
|
return Err(anyhow!("Unsupported assignment target at {:?}", assign.span));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Binary operations: evaluate both sides and apply the opcode
|
||||||
Expression::BinaryExpression(bin) => {
|
Expression::BinaryExpression(bin) => {
|
||||||
self.compile_expr(&bin.left)?;
|
self.compile_expr(&bin.left)?;
|
||||||
self.compile_expr(&bin.right)?;
|
self.compile_expr(&bin.right)?;
|
||||||
@ -340,6 +410,7 @@ impl Codegen {
|
|||||||
};
|
};
|
||||||
self.emit_op(op, vec![], bin.span);
|
self.emit_op(op, vec![], bin.span);
|
||||||
}
|
}
|
||||||
|
// Logical operations: evaluate both sides and apply the opcode
|
||||||
Expression::LogicalExpression(log) => {
|
Expression::LogicalExpression(log) => {
|
||||||
self.compile_expr(&log.left)?;
|
self.compile_expr(&log.left)?;
|
||||||
self.compile_expr(&log.right)?;
|
self.compile_expr(&log.right)?;
|
||||||
@ -350,6 +421,7 @@ impl Codegen {
|
|||||||
};
|
};
|
||||||
self.emit_op(op, vec![], log.span);
|
self.emit_op(op, vec![], log.span);
|
||||||
}
|
}
|
||||||
|
// Unary operations: evaluate argument and apply the opcode
|
||||||
Expression::UnaryExpression(unary) => {
|
Expression::UnaryExpression(unary) => {
|
||||||
self.compile_expr(&unary.argument)?;
|
self.compile_expr(&unary.argument)?;
|
||||||
let op = match unary.operator {
|
let op = match unary.operator {
|
||||||
@ -360,19 +432,18 @@ impl Codegen {
|
|||||||
};
|
};
|
||||||
self.emit_op(op, vec![], unary.span);
|
self.emit_op(op, vec![], unary.span);
|
||||||
}
|
}
|
||||||
|
// Function calls: resolve to Syscall or Call
|
||||||
Expression::CallExpression(call) => {
|
Expression::CallExpression(call) => {
|
||||||
let name = ast_util::get_callee_name(&call.callee)?;
|
let name = ast_util::get_callee_name(&call.callee)?;
|
||||||
if let Some(syscall_id) = syscall_map::map_syscall(&name) {
|
if let Some(syscall_id) = syscall_map::map_syscall(&name) {
|
||||||
if syscall_id == 0xFFFF_FFFF {
|
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 {
|
if call.arguments.len() != 3 {
|
||||||
return Err(anyhow!("Color.rgb expects 3 arguments at {:?}", call.span));
|
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() {
|
if let Some(expr) = call.arguments[0].as_expression() {
|
||||||
self.compile_expr(expr)?;
|
self.compile_expr(expr)?;
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(3)], call.span);
|
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);
|
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() {
|
if let Some(expr) = call.arguments[1].as_expression() {
|
||||||
self.compile_expr(expr)?;
|
self.compile_expr(expr)?;
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(2)], call.span);
|
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);
|
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() {
|
if let Some(expr) = call.arguments[2].as_expression() {
|
||||||
self.compile_expr(expr)?;
|
self.compile_expr(expr)?;
|
||||||
self.emit_op(OpCode::PushI32, vec![Operand::I32(3)], call.span);
|
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);
|
self.emit_op(OpCode::BitOr, vec![], call.span);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// Standard System Call
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
if let Some(expr) = arg.as_expression() {
|
if let Some(expr) = arg.as_expression() {
|
||||||
self.compile_expr(expr)?;
|
self.compile_expr(expr)?;
|
||||||
@ -410,7 +482,7 @@ impl Codegen {
|
|||||||
self.emit_op(OpCode::Syscall, vec![Operand::U32(syscall_id)], call.span);
|
self.emit_op(OpCode::Syscall, vec![Operand::U32(syscall_id)], call.span);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Local function call
|
// Local function call (to a function defined in the project)
|
||||||
for arg in &call.arguments {
|
for arg in &call.arguments {
|
||||||
if let Some(expr) = arg.as_expression() {
|
if let Some(expr) = arg.as_expression() {
|
||||||
self.compile_expr(expr)?;
|
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);
|
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) => {
|
Expression::StaticMemberExpression(member) => {
|
||||||
let full_name = ast_util::get_member_expr_name(expr)?;
|
let full_name = ast_util::get_member_expr_name(expr)?;
|
||||||
|
|
||||||
if full_name.to_lowercase().starts_with("color.") {
|
if full_name.to_lowercase().starts_with("color.") {
|
||||||
|
// Resolved at compile-time to literal values
|
||||||
match full_name.to_lowercase().as_str() {
|
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.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),
|
"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.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.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.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)),
|
_ => return Err(anyhow!("Unsupported color constant: {} at {:?}", full_name, member.span)),
|
||||||
}
|
}
|
||||||
} else if full_name.to_lowercase().starts_with("pad.") {
|
} else if full_name.to_lowercase().starts_with("pad.") {
|
||||||
|
// Re-mapped to specific input syscalls
|
||||||
let parts: Vec<&str> = full_name.split('.').collect();
|
let parts: Vec<&str> = full_name.split('.').collect();
|
||||||
if parts.len() == 3 {
|
if parts.len() == 3 {
|
||||||
let btn_name = parts[1];
|
let btn_name = parts[1];
|
||||||
let state_name = parts[2];
|
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 {
|
let syscall_id = match state_name {
|
||||||
"down" => Syscall::InputGetPad as u32,
|
"down" => Syscall::InputGetPad as u32,
|
||||||
"pressed" => Syscall::InputGetPadPressed 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));
|
return Err(anyhow!("Partial Pad access not supported: {} at {:?}", full_name, member.span));
|
||||||
}
|
}
|
||||||
} else if full_name.to_lowercase().starts_with("touch.") {
|
} else if full_name.to_lowercase().starts_with("touch.") {
|
||||||
|
// Re-mapped to specific touch syscalls
|
||||||
let parts: Vec<&str> = full_name.split('.').collect();
|
let parts: Vec<&str> = full_name.split('.').collect();
|
||||||
match parts.len() {
|
match parts.len() {
|
||||||
2 => {
|
2 => {
|
||||||
@ -490,39 +566,25 @@ impl Codegen {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_btn_name(&self, btn_name: &str) -> Result<u32> {
|
/// Generates a new unique label name for control flow.
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_label(&mut self, prefix: &str) -> String {
|
fn new_label(&mut self, prefix: &str) -> String {
|
||||||
let label = format!("{}_{}", prefix, self.label_count);
|
let label = format!("{}_{}", prefix, self.label_count);
|
||||||
self.label_count += 1;
|
self.label_count += 1;
|
||||||
label
|
label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a label definition in the instruction stream.
|
||||||
fn emit_label(&mut self, name: String) {
|
fn emit_label(&mut self, name: String) {
|
||||||
self.instructions.push((Asm::Label(name), false));
|
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<Operand>, span: Span) {
|
fn emit_op(&mut self, opcode: OpCode, operands: Vec<Operand>, span: Span) {
|
||||||
let has_symbol = if !span.is_unspanned() {
|
let has_symbol = if !span.is_unspanned() {
|
||||||
let (line, col) = self.lookup_pos(span.start);
|
let (line, col) = self.lookup_pos(span.start);
|
||||||
self.symbols.push(Symbol {
|
self.symbols.push(Symbol {
|
||||||
pc: 0,
|
pc: 0, // Will be resolved during finalize_symbols
|
||||||
file: self.file_name.clone(),
|
file: self.file_name.clone(),
|
||||||
line,
|
line,
|
||||||
col,
|
col,
|
||||||
@ -534,6 +596,7 @@ impl Codegen {
|
|||||||
self.instructions.push((Asm::Op(opcode, operands), has_symbol));
|
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) {
|
fn lookup_pos(&self, pos: u32) -> (usize, usize) {
|
||||||
let mut line = 1;
|
let mut line = 1;
|
||||||
let mut col = 1;
|
let mut col = 1;
|
||||||
@ -551,6 +614,9 @@ impl Codegen {
|
|||||||
(line, col)
|
(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) {
|
fn finalize_symbols(&mut self) {
|
||||||
let mut current_pc = 0u32;
|
let mut current_pc = 0u32;
|
||||||
let mut symbol_ptr = 0;
|
let mut symbol_ptr = 0;
|
||||||
@ -564,7 +630,7 @@ impl Codegen {
|
|||||||
symbol_ptr += 1;
|
symbol_ptr += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
current_pc += 2;
|
current_pc += 2; // OpCode size (u16)
|
||||||
current_pc = asm::update_pc_by_operand(current_pc, operands);
|
current_pc = asm::update_pc_by_operand(current_pc, operands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 codegen;
|
||||||
pub mod validator;
|
pub mod validator;
|
||||||
pub mod ast_util;
|
pub mod ast_util;
|
||||||
|
pub mod syscall_map;
|
||||||
|
mod input_map;
|
||||||
|
|
||||||
pub use codegen::Codegen;
|
pub use codegen::Codegen;
|
||||||
20
crates/prometeu-compiler/src/codegen/syscall_map.rs
Normal file
20
crates/prometeu-compiler/src/codegen/syscall_map.rs
Normal file
@ -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 <id>` opcode.
|
||||||
|
pub fn map_syscall(name: &str) -> Option<u32> {
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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::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 {
|
pub struct Validator {
|
||||||
|
/// List of validation errors found during the pass.
|
||||||
errors: Vec<String>,
|
errors: Vec<String>,
|
||||||
|
/// Set of function names defined in the project, used to distinguish
|
||||||
|
/// local calls from invalid references.
|
||||||
local_functions: std::collections::HashSet<String>,
|
local_functions: std::collections::HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Validator {
|
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<()> {
|
pub fn validate(program: &Program) -> Result<()> {
|
||||||
let mut validator = Self {
|
let mut validator = Self {
|
||||||
errors: Vec::new(),
|
errors: Vec::new(),
|
||||||
local_functions: std::collections::HashSet::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 {
|
for item in &program.body {
|
||||||
match item {
|
match item {
|
||||||
Statement::FunctionDeclaration(f) => {
|
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);
|
validator.visit_program(program);
|
||||||
|
|
||||||
if validator.errors.is_empty() {
|
if validator.errors.is_empty() {
|
||||||
@ -65,6 +77,7 @@ impl Validator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Visit<'a> for Validator {
|
impl<'a> Visit<'a> for Validator {
|
||||||
|
/// Validates that only supported statements are used.
|
||||||
fn visit_statement(&mut self, stmt: &Statement<'a>) {
|
fn visit_statement(&mut self, stmt: &Statement<'a>) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::VariableDeclaration(_) |
|
Statement::VariableDeclaration(_) |
|
||||||
@ -74,15 +87,16 @@ impl<'a> Visit<'a> for Validator {
|
|||||||
Statement::ExportNamedDeclaration(_) |
|
Statement::ExportNamedDeclaration(_) |
|
||||||
Statement::ImportDeclaration(_) |
|
Statement::ImportDeclaration(_) |
|
||||||
Statement::FunctionDeclaration(_) => {
|
Statement::FunctionDeclaration(_) => {
|
||||||
// Supported
|
// These are the only statements the PVM handles currently.
|
||||||
walk::walk_statement(self, stmt);
|
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>) {
|
fn visit_expression(&mut self, expr: &Expression<'a>) {
|
||||||
match expr {
|
match expr {
|
||||||
Expression::NumericLiteral(_) |
|
Expression::NumericLiteral(_) |
|
||||||
@ -96,11 +110,11 @@ impl<'a> Visit<'a> for Validator {
|
|||||||
Expression::UnaryExpression(_) |
|
Expression::UnaryExpression(_) |
|
||||||
Expression::CallExpression(_) |
|
Expression::CallExpression(_) |
|
||||||
Expression::StaticMemberExpression(_) => {
|
Expression::StaticMemberExpression(_) => {
|
||||||
// Base types supported, detailed checks in specific visit methods if needed
|
// Basic JS logic is supported.
|
||||||
walk::walk_expression(self, expr);
|
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,43 +1,62 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use crate::codegen::validator::Validator;
|
||||||
use anyhow::{Result, Context, anyhow};
|
use crate::codegen::Codegen;
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
use oxc_allocator::Allocator;
|
use oxc_allocator::Allocator;
|
||||||
|
use oxc_ast::ast::*;
|
||||||
use oxc_parser::Parser;
|
use oxc_parser::Parser;
|
||||||
use oxc_span::SourceType;
|
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 prometeu_bytecode::disasm::disasm;
|
||||||
use serde::Serialize;
|
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)]
|
#[derive(Serialize)]
|
||||||
pub struct Symbol {
|
pub struct Symbol {
|
||||||
|
/// The absolute address in the bytecode.
|
||||||
pub pc: u32,
|
pub pc: u32,
|
||||||
|
/// The original source file path.
|
||||||
pub file: String,
|
pub file: String,
|
||||||
|
/// 1-based line number.
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
|
/// 1-based column number.
|
||||||
pub col: usize,
|
pub col: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of a successful compilation process.
|
||||||
|
/// It contains the final binary and the metadata needed for debugging.
|
||||||
pub struct CompilationUnit {
|
pub struct CompilationUnit {
|
||||||
|
/// The raw binary data (PBC format).
|
||||||
pub rom: Vec<u8>,
|
pub rom: Vec<u8>,
|
||||||
|
/// The list of debug symbols for source mapping.
|
||||||
pub symbols: Vec<Symbol>,
|
pub symbols: Vec<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilationUnit {
|
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<()> {
|
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))?;
|
fs::write(out, &self.rom).with_context(|| format!("Failed to write PBC to {:?}", out))?;
|
||||||
|
|
||||||
|
// 2. Export symbols for the HostDebugger
|
||||||
if emit_symbols {
|
if emit_symbols {
|
||||||
let symbols_path = out.with_file_name("symbols.json");
|
let symbols_path = out.with_file_name("symbols.json");
|
||||||
let symbols_json = serde_json::to_string_pretty(&self.symbols)?;
|
let symbols_json = serde_json::to_string_pretty(&self.symbols)?;
|
||||||
fs::write(&symbols_path, symbols_json)?;
|
fs::write(&symbols_path, symbols_json)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Export human-readable disassembly for developer inspection
|
||||||
if emit_disasm {
|
if emit_disasm {
|
||||||
let disasm_path = out.with_extension("disasm.txt");
|
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) {
|
let rom_to_disasm = if let Ok(pbc) = prometeu_bytecode::pbc::parse_pbc(&self.rom) {
|
||||||
pbc.rom
|
pbc.rom
|
||||||
} else {
|
} else {
|
||||||
@ -48,6 +67,7 @@ impl CompilationUnit {
|
|||||||
|
|
||||||
let mut disasm_text = String::new();
|
let mut disasm_text = String::new();
|
||||||
for instr in instructions {
|
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 symbol = self.symbols.iter().find(|s| s.pc == instr.pc);
|
||||||
let comment = if let Some(s) = symbol {
|
let comment = if let Some(s) = symbol {
|
||||||
format!(" ; {}:{}", s.file, s.line)
|
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<PathBuf> {
|
fn resolve_import(base_path: &Path, import_str: &str) -> Result<PathBuf> {
|
||||||
let mut path = base_path.parent().unwrap().join(import_str);
|
let mut path = base_path.parent().unwrap().join(import_str);
|
||||||
|
|
||||||
|
// Auto-append extensions if missing
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
if path.with_extension("ts").exists() {
|
if path.with_extension("ts").exists() {
|
||||||
path.set_extension("ts");
|
path.set_extension("ts");
|
||||||
@ -78,36 +101,48 @@ fn resolve_import(base_path: &Path, import_str: &str) -> Result<PathBuf> {
|
|||||||
path.set_extension("js");
|
path.set_extension("js");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(anyhow!("Cannot resolve import '{}' from {:?}", import_str, base_path));
|
return Err(anyhow!("Cannot resolve import '{}' from {:?}", import_str, base_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(path.canonicalize()?)
|
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<CompilationUnit> {
|
pub fn compile(entry: &Path) -> Result<CompilationUnit> {
|
||||||
let allocator = Allocator::default();
|
let allocator = Allocator::default();
|
||||||
let mut modules = HashMap::new();
|
let mut modules = HashMap::new();
|
||||||
let mut queue = VecDeque::new();
|
let mut queue = VecDeque::new();
|
||||||
|
|
||||||
|
// Start with the entry point
|
||||||
let entry_abs = entry.canonicalize()
|
let entry_abs = entry.canonicalize()
|
||||||
.with_context(|| format!("Failed to canonicalize entry path: {:?}", entry))?;
|
.with_context(|| format!("Failed to canonicalize entry path: {:?}", entry))?;
|
||||||
queue.push_back(entry_abs.clone());
|
queue.push_back(entry_abs.clone());
|
||||||
|
|
||||||
|
// --- PHASE 1: Dependency Resolution and Parsing ---
|
||||||
while let Some(path) = queue.pop_front() {
|
while let Some(path) = queue.pop_front() {
|
||||||
let path_str = path.to_string_lossy().to_string();
|
let path_str = path.to_string_lossy().to_string();
|
||||||
if modules.contains_key(&path_str) {
|
if modules.contains_key(&path_str) {
|
||||||
continue;
|
continue; // Already processed
|
||||||
}
|
}
|
||||||
|
|
||||||
let source_text = fs::read_to_string(&path)
|
let source_text = fs::read_to_string(&path)
|
||||||
.with_context(|| format!("Failed to read file: {:?}", 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_text_ptr: &str = allocator.alloc_str(&source_text);
|
||||||
|
|
||||||
let source_type = SourceType::from_path(&path).unwrap_or_default();
|
let source_type = SourceType::from_path(&path).unwrap_or_default();
|
||||||
let parser_ret = Parser::new(&allocator, source_text_ptr, source_type).parse();
|
let parser_ret = Parser::new(&allocator, source_text_ptr, source_type).parse();
|
||||||
|
|
||||||
|
// Handle syntax errors immediately
|
||||||
if !parser_ret.errors.is_empty() {
|
if !parser_ret.errors.is_empty() {
|
||||||
for error in parser_ret.errors {
|
for error in parser_ret.errors {
|
||||||
eprintln!("{:?}", error);
|
eprintln!("{:?}", error);
|
||||||
@ -115,10 +150,11 @@ pub fn compile(entry: &Path) -> Result<CompilationUnit> {
|
|||||||
return Err(anyhow!("Failed to parse module: {:?}", path));
|
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)?;
|
Validator::validate(&parser_ret.program)?;
|
||||||
|
|
||||||
// Find imports to add to queue
|
// Discover new imports to crawl
|
||||||
for item in &parser_ret.program.body {
|
for item in &parser_ret.program.body {
|
||||||
if let Statement::ImportDeclaration(decl) = item {
|
if let Statement::ImportDeclaration(decl) = item {
|
||||||
let import_path = decl.source.value.as_str();
|
let import_path = decl.source.value.as_str();
|
||||||
@ -130,14 +166,15 @@ pub fn compile(entry: &Path) -> Result<CompilationUnit> {
|
|||||||
modules.insert(path_str, (source_text_ptr, parser_ret.program));
|
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 entry_str = entry_abs.to_string_lossy().to_string();
|
||||||
let mut program_list = Vec::new();
|
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"))?;
|
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));
|
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 {
|
for (path, (source, program)) in &modules {
|
||||||
if path != &entry_str {
|
if path != &entry_str {
|
||||||
program_list.push((path.clone(), source.to_string(), program));
|
program_list.push((path.clone(), source.to_string(), program));
|
||||||
|
|||||||
@ -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 codegen;
|
||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
pub mod syscall_map;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use anyhow::Result;
|
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<PathBuf>,
|
||||||
|
|
||||||
|
/// Path to save the compiled .pbc file.
|
||||||
|
#[arg(short, long)]
|
||||||
|
out: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// 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<()> {
|
pub fn run() -> Result<()> {
|
||||||
let cli = cli::Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
cli::Commands::Build {
|
Commands::Build {
|
||||||
project_dir,
|
project_dir,
|
||||||
entry,
|
entry,
|
||||||
out,
|
out,
|
||||||
@ -32,7 +91,7 @@ pub fn run() -> Result<()> {
|
|||||||
let compilation_unit = compiler::compile(&entry)?;
|
let compilation_unit = compiler::compile(&entry)?;
|
||||||
compilation_unit.export(&out, emit_disasm, emit_symbols)?;
|
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");
|
let entry = project_dir.join("src/main.ts");
|
||||||
println!("Verifying project at {:?}", project_dir);
|
println!("Verifying project at {:?}", project_dir);
|
||||||
println!("Entry: {:?}", entry);
|
println!("Entry: {:?}", entry);
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
/// Main entry point for the Prometeu Compiler binary.
|
||||||
|
/// It delegates execution to the library's `run` function.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
prometeu_compiler::run()
|
prometeu_compiler::run()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<u32> {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use crate::model::AppMode;
|
use crate::model::AppMode;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::virtual_machine::Value;
|
use crate::virtual_machine::Value;
|
||||||
|
|
||||||
@ -80,6 +80,7 @@ pub enum DebugEvent {
|
|||||||
vm_steps: u32,
|
vm_steps: u32,
|
||||||
syscalls: u32,
|
syscalls: u32,
|
||||||
cycles: u64,
|
cycles: u64,
|
||||||
|
cycles_budget: u64,
|
||||||
host_cpu_time_us: u64,
|
host_cpu_time_us: u64,
|
||||||
violations: u32,
|
violations: u32,
|
||||||
gfx_used_bytes: usize,
|
gfx_used_bytes: usize,
|
||||||
@ -103,6 +104,30 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::virtual_machine::Value;
|
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]
|
#[test]
|
||||||
fn test_get_state_serialization() {
|
fn test_get_state_serialization() {
|
||||||
let resp = DebugResponse::GetState {
|
let resp = DebugResponse::GetState {
|
||||||
|
|||||||
@ -11,21 +11,31 @@ use crate::telemetry::CertificationConfig;
|
|||||||
|
|
||||||
/// PROMETEU Firmware.
|
/// PROMETEU Firmware.
|
||||||
///
|
///
|
||||||
/// The central orchestrator of the system. It manages the high-level state machine,
|
/// The central orchestrator of the console. The firmware acts as the
|
||||||
/// transitioning between system states like booting, the Hub launcher, and
|
/// "Control Unit", managing the high-level state machine of the system.
|
||||||
/// actual application execution.
|
///
|
||||||
|
/// 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 {
|
pub struct Firmware {
|
||||||
/// The Virtual Machine instance.
|
/// The execution engine (PVM) for user applications.
|
||||||
pub vm: VirtualMachine,
|
pub vm: VirtualMachine,
|
||||||
/// The System Operating logic (syscalls, telemetry, logs).
|
/// The underlying OS services (Syscalls, Filesystem, Telemetry).
|
||||||
pub os: PrometeuOS,
|
pub os: PrometeuOS,
|
||||||
/// The System UI / Launcher environment.
|
/// The internal state of the system launcher (Hub).
|
||||||
pub hub: PrometeuHub,
|
pub hub: PrometeuHub,
|
||||||
/// Current high-level state of the system.
|
/// The current operational state (e.g., Reset, SplashScreen, GameRunning).
|
||||||
pub state: FirmwareState,
|
pub state: FirmwareState,
|
||||||
/// Desired execution target resolved at boot.
|
/// The desired application to run after boot (Hub or specific Cartridge).
|
||||||
pub boot_target: BootTarget,
|
pub boot_target: BootTarget,
|
||||||
/// Tracking flag to ensure `on_enter` is called exactly once per state transition.
|
/// State-machine lifecycle tracker.
|
||||||
state_initialized: bool,
|
state_initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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_reset::ResetStep;
|
||||||
pub use crate::firmware::firmware_step_splash_screen::SplashScreenStep;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FirmwareState {
|
pub enum FirmwareState {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep};
|
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep};
|
||||||
use crate::firmware::prometeu_context::PrometeuContext;
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
use crate::model::Color;
|
|
||||||
use crate::log::{LogLevel, LogSource};
|
use crate::log::{LogLevel, LogSource};
|
||||||
|
use crate::model::Color;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AppCrashesStep {
|
pub struct AppCrashesStep {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use crate::firmware::boot_target::BootTarget;
|
use crate::firmware::boot_target::BootTarget;
|
||||||
use crate::firmware::firmware_state::{FirmwareState, HubHomeStep, LoadCartridgeStep};
|
use crate::firmware::firmware_state::{FirmwareState, HubHomeStep, LoadCartridgeStep};
|
||||||
use crate::firmware::prometeu_context::PrometeuContext;
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
use crate::model::CartridgeLoader;
|
|
||||||
use crate::log::{LogLevel, LogSource};
|
use crate::log::{LogLevel, LogSource};
|
||||||
|
use crate::model::CartridgeLoader;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LaunchHubStep;
|
pub struct LaunchHubStep;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::firmware::firmware_state::{FirmwareState, GameRunningStep, HubHomeStep};
|
use crate::firmware::firmware_state::{FirmwareState, GameRunningStep, HubHomeStep};
|
||||||
use crate::firmware::prometeu_context::PrometeuContext;
|
use crate::firmware::prometeu_context::PrometeuContext;
|
||||||
use crate::model::{AppMode, Cartridge, Color, Rect};
|
|
||||||
use crate::log::{LogLevel, LogSource};
|
use crate::log::{LogLevel, LogSource};
|
||||||
|
use crate::model::{AppMode, Cartridge, Color, Rect};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LoadCartridgeStep {
|
pub struct LoadCartridgeStep {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ pub(crate) mod firmware_step_game_running;
|
|||||||
pub(crate) mod firmware_step_crash_screen;
|
pub(crate) mod firmware_step_crash_screen;
|
||||||
mod prometeu_context;
|
mod prometeu_context;
|
||||||
|
|
||||||
|
pub use boot_target::BootTarget;
|
||||||
pub use firmware::Firmware;
|
pub use firmware::Firmware;
|
||||||
pub use firmware_state::FirmwareState;
|
pub use firmware_state::FirmwareState;
|
||||||
pub use prometeu_context::PrometeuContext;
|
pub use prometeu_context::PrometeuContext;
|
||||||
pub use boot_target::BootTarget;
|
|
||||||
|
|||||||
@ -4,8 +4,8 @@ mod fs_entry;
|
|||||||
mod fs_backend;
|
mod fs_backend;
|
||||||
mod virtual_fs;
|
mod virtual_fs;
|
||||||
|
|
||||||
|
pub use fs_backend::FsBackend;
|
||||||
|
pub use fs_entry::FsEntry;
|
||||||
pub use fs_error::FsError;
|
pub use fs_error::FsError;
|
||||||
pub use fs_state::FsState;
|
pub use fs_state::FsState;
|
||||||
pub use fs_entry::FsEntry;
|
|
||||||
pub use fs_backend::FsBackend;
|
|
||||||
pub use virtual_fs::VirtualFS;
|
pub use virtual_fs::VirtualFS;
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
use crate::fs::{FsBackend, FsEntry, FsError};
|
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 {
|
pub struct VirtualFS {
|
||||||
|
/// The active storage implementation.
|
||||||
backend: Option<Box<dyn FsBackend>>,
|
backend: Option<Box<dyn FsBackend>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::hardware::memory_banks::{TileBankPoolInstaller, SoundBankPoolInstaller};
|
use crate::hardware::memory_banks::{SoundBankPoolInstaller, TileBankPoolInstaller};
|
||||||
use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, SlotRef, SlotStats, TileBank, TileSize, SoundBank, Sample, PreloadEntry};
|
use crate::model::{AssetEntry, BankStats, BankType, Color, HandleId, LoadStatus, PreloadEntry, Sample, SlotRef, SlotStats, SoundBank, TileBank, TileSize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@ -659,7 +659,7 @@ impl AssetManager {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::hardware::memory_banks::{TileBankPoolAccess, SoundBankPoolAccess, MemoryBanks};
|
use crate::hardware::memory_banks::{MemoryBanks, SoundBankPoolAccess, TileBankPoolAccess};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_asset_loading_flow() {
|
fn test_asset_loading_flow() {
|
||||||
|
|||||||
@ -97,15 +97,25 @@ pub enum AudioCommand {
|
|||||||
|
|
||||||
/// PROMETEU Audio Subsystem.
|
/// PROMETEU Audio Subsystem.
|
||||||
///
|
///
|
||||||
/// Models a multi-channel PCM sampler.
|
/// Models a multi-channel PCM sampler (SPU).
|
||||||
/// It works like an "Audio CPU": the Game Core sends high-level commands
|
/// The audio system in Prometeu is **command-based**. This means the Core
|
||||||
/// every frame, and the Host backend implements the low-level mixer.
|
/// 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 {
|
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],
|
pub voices: [Channel; MAX_CHANNELS],
|
||||||
/// Queue of pending commands to be processed by the Host mixer.
|
/// 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<AudioCommand>,
|
pub commands: Vec<AudioCommand>,
|
||||||
/// Interface to access sound memory banks.
|
/// Interface to access sound memory banks (ARAM).
|
||||||
pub sound_banks: Arc<dyn SoundBankPoolAccess>,
|
pub sound_banks: Arc<dyn SoundBankPoolAccess>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,18 +4,24 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
/// Blending modes inspired by classic 16-bit hardware.
|
/// Blending modes inspired by classic 16-bit hardware.
|
||||||
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
/// Defines how source pixels are combined with existing pixels in the framebuffer.
|
||||||
|
///
|
||||||
|
/// ### Usage Example:
|
||||||
|
/// ```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)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
/// No blending: source overwrites destination.
|
/// No blending: a source overwrites the destination.
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
/// Average: dst = (src + dst) / 2. Creates a semi-transparent effect.
|
/// Average: `dst = (src + dst) / 2`. Creates a semi-transparent effect.
|
||||||
Half,
|
Half,
|
||||||
/// Additive: dst = dst + (src / 2). Good for glows/light.
|
/// Additive: `dst = clamp(dst + (src / 2))`. Good for glows/light.
|
||||||
HalfPlus,
|
HalfPlus,
|
||||||
/// Subtractive: dst = dst - (src / 2). Good for shadows.
|
/// Subtractive: `dst = clamp(dst - (src / 2))`. Good for shadows.
|
||||||
HalfMinus,
|
HalfMinus,
|
||||||
/// Full Additive: dst = dst + src. Saturated light effect.
|
/// Full Additive: `dst = clamp(dst + src)`. Saturated light effect.
|
||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,44 +30,48 @@ pub enum BlendMode {
|
|||||||
/// Models a specialized graphics chip with a fixed resolution, double buffering,
|
/// Models a specialized graphics chip with a fixed resolution, double buffering,
|
||||||
/// and a multi-layered tile/sprite architecture.
|
/// and a multi-layered tile/sprite architecture.
|
||||||
///
|
///
|
||||||
/// Composition Order (back to front):
|
/// The GFX system works by composing several "layers" into a single 16-bit
|
||||||
/// 1. Sprites with priority 0 (optional background objects)
|
/// RGB565 framebuffer. It supports hardware-accelerated primitives (lines, rects)
|
||||||
/// 2. Tile Layer 0 + Sprites with priority 1
|
/// and specialized console features like background scrolling and sprite sorting.
|
||||||
/// 3. Tile Layer 1 + Sprites with priority 2
|
///
|
||||||
/// 4. Tile Layer 2 + Sprites with priority 3
|
/// ### Layer Composition Order (back to front):
|
||||||
/// 5. Tile Layer 3 + Sprites with priority 4
|
/// 1. **Priority 0 Sprites**: Objects behind everything else.
|
||||||
/// 6. Scene Fade effect
|
/// 2. **Tile Layer 0 + Priority 1 Sprites**: Background 0.
|
||||||
/// 7. HUD Layer (always on top)
|
/// 3. **Tile Layer 1 + Priority 2 Sprites**: Background 1.
|
||||||
/// 8. HUD Fade effect
|
/// 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 {
|
pub struct Gfx {
|
||||||
/// Width of the internal framebuffer.
|
/// Width of the internal framebuffer in pixels.
|
||||||
w: usize,
|
w: usize,
|
||||||
/// Height of the internal framebuffer.
|
/// Height of the internal framebuffer in pixels.
|
||||||
h: usize,
|
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<u16>,
|
front: Vec<u16>,
|
||||||
/// Back buffer: the one being drawn to during the current frame.
|
/// Back buffer: the "Work RAM" where new frames are composed.
|
||||||
back: Vec<u16>,
|
back: Vec<u16>,
|
||||||
|
|
||||||
/// 4 scrollable backgrounds.
|
/// 4 scrollable background layers. Each can have its own scroll (X, Y) and TileBank.
|
||||||
pub layers: [ScrollableTileLayer; 4],
|
pub layers: [ScrollableTileLayer; 4],
|
||||||
/// 1 fixed layer for User Interface.
|
/// 1 fixed layer for User Interface (HUD). It doesn't scroll.
|
||||||
pub hud: HudTileLayer,
|
pub hud: HudTileLayer,
|
||||||
/// Interface to access graphical memory banks.
|
/// Shared access to graphical memory banks (tiles and palettes).
|
||||||
pub tile_banks: Arc<dyn TileBankPoolAccess>,
|
pub tile_banks: Arc<dyn TileBankPoolAccess>,
|
||||||
/// Hardware sprites (Object Attribute Memory equivalent).
|
/// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory).
|
||||||
pub sprites: [Sprite; 512],
|
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,
|
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,
|
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,
|
pub hud_fade_level: u8,
|
||||||
/// Color used for the HUD fade effect.
|
/// Target color for the HUD fade effect.
|
||||||
pub hud_fade_color: Color,
|
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<usize>; 5],
|
priority_buckets: [Vec<usize>; 5],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,29 @@
|
|||||||
use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, Pad, Touch, MemoryBanks};
|
use crate::hardware::memory_banks::{SoundBankPoolAccess, SoundBankPoolInstaller, TileBankPoolAccess, TileBankPoolInstaller};
|
||||||
use crate::hardware::memory_banks::{TileBankPoolAccess, TileBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller};
|
use crate::hardware::{AssetManager, Audio, Gfx, HardwareBridge, MemoryBanks, Pad, Touch};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Aggregate structure for all virtual hardware peripherals.
|
/// Aggregate structure for all virtual hardware peripherals.
|
||||||
///
|
///
|
||||||
/// This struct represents the "Mainboard" of the PROMETEU console,
|
/// This struct represents the "Mainboard" of the PROMETEU console.
|
||||||
/// containing instances of GFX, Audio, Input (Pad), and Touch.
|
/// 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 {
|
pub struct Hardware {
|
||||||
/// Shared memory banks for hardware assets.
|
/// The Graphics Processing Unit (GPU). Handles drawing primitives, sprites, and tilemaps.
|
||||||
pub memory_banks: Arc<MemoryBanks>,
|
|
||||||
/// The Graphics Processing Unit.
|
|
||||||
pub gfx: Gfx,
|
pub gfx: Gfx,
|
||||||
/// The Sound Processing Unit.
|
/// The Sound Processing Unit (SPU). Manages sample playback and volume control.
|
||||||
pub audio: Audio,
|
pub audio: Audio,
|
||||||
/// The standard digital gamepad.
|
/// The standard digital gamepad. Provides state for D-Pad, face buttons, and triggers.
|
||||||
pub pad: Pad,
|
pub pad: Pad,
|
||||||
/// The absolute pointer input device.
|
/// The absolute pointer input device (Mouse/Touchscreen).
|
||||||
pub touch: Touch,
|
pub touch: Touch,
|
||||||
/// The Asset Management system.
|
/// The Asset Management system (DMA). Handles loading data into VRAM (TileBanks) and ARAM (SoundBanks).
|
||||||
pub assets: AssetManager,
|
pub assets: AssetManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +54,6 @@ impl Hardware {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let memory_banks = Arc::new(MemoryBanks::new());
|
let memory_banks = Arc::new(MemoryBanks::new());
|
||||||
Self {
|
Self {
|
||||||
memory_banks: Arc::clone(&memory_banks),
|
|
||||||
gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc<dyn TileBankPoolAccess>),
|
gfx: Gfx::new(Self::W, Self::H, Arc::clone(&memory_banks) as Arc<dyn TileBankPoolAccess>),
|
||||||
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
|
audio: Audio::new(Arc::clone(&memory_banks) as Arc<dyn SoundBankPoolAccess>),
|
||||||
pad: Pad::default(),
|
pad: Pad::default(),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
use crate::model::{SoundBank, TileBank};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use crate::model::{TileBank, SoundBank};
|
|
||||||
|
|
||||||
/// Non-generic interface for peripherals to access graphical tile banks.
|
/// Non-generic interface for peripherals to access graphical tile banks.
|
||||||
pub trait TileBankPoolAccess: Send + Sync {
|
pub trait TileBankPoolAccess: Send + Sync {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ mod input_signal;
|
|||||||
mod audio;
|
mod audio;
|
||||||
mod memory_banks;
|
mod memory_banks;
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
|
pub mod syscalls;
|
||||||
|
|
||||||
pub use crate::model::HandleId;
|
pub use crate::model::HandleId;
|
||||||
pub use asset::AssetManager;
|
pub use asset::AssetManager;
|
||||||
@ -15,6 +16,7 @@ pub use gfx::Gfx;
|
|||||||
pub use input_signal::InputSignals;
|
pub use input_signal::InputSignals;
|
||||||
pub use memory_banks::MemoryBanks;
|
pub use memory_banks::MemoryBanks;
|
||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
|
pub use syscalls::Syscall;
|
||||||
pub use touch::Touch;
|
pub use touch::Touch;
|
||||||
|
|
||||||
pub trait HardwareBridge {
|
pub trait HardwareBridge {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::model::Button;
|
|
||||||
use crate::hardware::input_signal::InputSignals;
|
use crate::hardware::input_signal::InputSignals;
|
||||||
|
use crate::model::Button;
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct Pad {
|
pub struct Pad {
|
||||||
|
|||||||
@ -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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Syscall {
|
pub enum Syscall {
|
||||||
// System
|
// --- System ---
|
||||||
|
/// Checks if a cartridge is currently inserted in the virtual slot.
|
||||||
SystemHasCart = 0x0001,
|
SystemHasCart = 0x0001,
|
||||||
|
/// Requests the OS to launch a specific cartridge.
|
||||||
SystemRunCart = 0x0002,
|
SystemRunCart = 0x0002,
|
||||||
|
|
||||||
// GFX
|
// --- GFX (Graphics) ---
|
||||||
|
/// Fills the entire back buffer with a single color.
|
||||||
GfxClear = 0x1001,
|
GfxClear = 0x1001,
|
||||||
|
/// Draws a solid rectangle.
|
||||||
GfxFillRect = 0x1002,
|
GfxFillRect = 0x1002,
|
||||||
|
/// Draws a 1-pixel wide line.
|
||||||
GfxDrawLine = 0x1003,
|
GfxDrawLine = 0x1003,
|
||||||
|
/// Draws a circle outline.
|
||||||
GfxDrawCircle = 0x1004,
|
GfxDrawCircle = 0x1004,
|
||||||
|
/// Draws a filled circle (disc).
|
||||||
GfxDrawDisc = 0x1005,
|
GfxDrawDisc = 0x1005,
|
||||||
|
/// Draws a rectangle outline.
|
||||||
GfxDrawSquare = 0x1006,
|
GfxDrawSquare = 0x1006,
|
||||||
|
/// Configures one of the 512 hardware sprites.
|
||||||
GfxSetSprite = 0x1007,
|
GfxSetSprite = 0x1007,
|
||||||
|
|
||||||
// Input
|
// --- Input ---
|
||||||
|
/// Returns the current raw state of the digital gamepad (bitmask).
|
||||||
InputGetPad = 0x2001,
|
InputGetPad = 0x2001,
|
||||||
|
/// Returns buttons that were pressed exactly in this frame.
|
||||||
InputGetPadPressed = 0x2002,
|
InputGetPadPressed = 0x2002,
|
||||||
|
/// Returns buttons that were released exactly in this frame.
|
||||||
InputGetPadReleased = 0x2003,
|
InputGetPadReleased = 0x2003,
|
||||||
|
/// Returns how many frames a button has been held down.
|
||||||
InputGetPadHold = 0x2004,
|
InputGetPadHold = 0x2004,
|
||||||
|
|
||||||
|
/// Returns the X coordinate of the touch/mouse pointer.
|
||||||
TouchGetX = 0x2101,
|
TouchGetX = 0x2101,
|
||||||
|
/// Returns the Y coordinate of the touch/mouse pointer.
|
||||||
TouchGetY = 0x2102,
|
TouchGetY = 0x2102,
|
||||||
|
/// Returns true if the pointer is currently touching the screen.
|
||||||
TouchIsDown = 0x2103,
|
TouchIsDown = 0x2103,
|
||||||
|
/// Returns true if the touch started in this frame.
|
||||||
TouchIsPressed = 0x2104,
|
TouchIsPressed = 0x2104,
|
||||||
|
/// Returns true if the touch ended in this frame.
|
||||||
TouchIsReleased = 0x2105,
|
TouchIsReleased = 0x2105,
|
||||||
|
/// Returns how many frames the pointer has been held down.
|
||||||
TouchGetHold = 0x2106,
|
TouchGetHold = 0x2106,
|
||||||
|
|
||||||
// Audio
|
// --- Audio ---
|
||||||
|
/// Starts playback of a sound sample by its Bank and ID.
|
||||||
AudioPlaySample = 0x3001,
|
AudioPlaySample = 0x3001,
|
||||||
|
/// Low-level audio play command.
|
||||||
AudioPlay = 0x3002,
|
AudioPlay = 0x3002,
|
||||||
|
|
||||||
// FS
|
// --- FS (Filesystem) ---
|
||||||
|
/// Opens a file for reading or writing. Returns a File Handle (u32).
|
||||||
FsOpen = 0x4001,
|
FsOpen = 0x4001,
|
||||||
|
/// Reads data from an open file handle into the VM heap.
|
||||||
FsRead = 0x4002,
|
FsRead = 0x4002,
|
||||||
|
/// Writes data from the VM heap into an open file handle.
|
||||||
FsWrite = 0x4003,
|
FsWrite = 0x4003,
|
||||||
|
/// Closes an open file handle.
|
||||||
FsClose = 0x4004,
|
FsClose = 0x4004,
|
||||||
|
/// Lists entries in a directory.
|
||||||
FsListDir = 0x4005,
|
FsListDir = 0x4005,
|
||||||
|
/// Checks if a file or directory exists.
|
||||||
FsExists = 0x4006,
|
FsExists = 0x4006,
|
||||||
|
/// Deletes a file or empty directory.
|
||||||
FsDelete = 0x4007,
|
FsDelete = 0x4007,
|
||||||
|
|
||||||
// Log
|
// --- Log ---
|
||||||
|
/// Writes a generic string to the system log.
|
||||||
LogWrite = 0x5001,
|
LogWrite = 0x5001,
|
||||||
|
/// Writes a string to the system log with a specific numerical tag.
|
||||||
LogWriteTag = 0x5002,
|
LogWriteTag = 0x5002,
|
||||||
|
|
||||||
// Asset
|
// --- Asset (DMA) ---
|
||||||
|
/// Starts an asynchronous load of a file into a memory bank.
|
||||||
AssetLoad = 0x6001,
|
AssetLoad = 0x6001,
|
||||||
|
/// Returns the status of a pending asset load (0=Loading, 1=Ready, 2=Error).
|
||||||
AssetStatus = 0x6002,
|
AssetStatus = 0x6002,
|
||||||
|
/// Finalizes the asset loading, making it available for GFX/Audio.
|
||||||
AssetCommit = 0x6003,
|
AssetCommit = 0x6003,
|
||||||
|
/// Cancels a pending asset load.
|
||||||
AssetCancel = 0x6004,
|
AssetCancel = 0x6004,
|
||||||
|
|
||||||
// Bank
|
// --- Bank (Memory) ---
|
||||||
|
/// Returns information about a specific Memory Bank.
|
||||||
BankInfo = 0x6101,
|
BankInfo = 0x6101,
|
||||||
|
/// Returns information about a slot within a Memory Bank.
|
||||||
BankSlotInfo = 0x6102,
|
BankSlotInfo = 0x6102,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::model::Button;
|
|
||||||
use crate::hardware::input_signal::InputSignals;
|
use crate::hardware::input_signal::InputSignals;
|
||||||
|
use crate::model::Button;
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct Touch {
|
pub struct Touch {
|
||||||
|
|||||||
@ -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 hardware;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod virtual_machine;
|
pub mod virtual_machine;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
use crate::log::{LogEvent, LogLevel, LogSource};
|
use crate::log::{LogEvent, LogLevel, LogSource};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct LogService {
|
pub struct LogService {
|
||||||
events: VecDeque<LogEvent>,
|
events: VecDeque<LogEvent>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ mod log_source;
|
|||||||
mod log_event;
|
mod log_event;
|
||||||
mod log_service;
|
mod log_service;
|
||||||
|
|
||||||
pub use log_level::LogLevel;
|
|
||||||
pub use log_source::LogSource;
|
|
||||||
pub use log_event::LogEvent;
|
pub use log_event::LogEvent;
|
||||||
|
pub use log_level::LogLevel;
|
||||||
pub use log_service::LogService;
|
pub use log_service::LogService;
|
||||||
|
pub use log_source::LogSource;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
use crate::model::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::model::cartridge::{Cartridge, CartridgeDTO, CartridgeError, CartridgeManifest};
|
|
||||||
|
|
||||||
pub struct CartridgeLoader;
|
pub struct CartridgeLoader;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
/// Simple RGB565 color (0bRRRRRGGGGGGBBBBB).
|
/// Represents a 16-bit color in the RGB565 format.
|
||||||
/// - 5 bits Red
|
|
||||||
/// - 6 bits Green
|
|
||||||
/// - 5 bits Blue
|
|
||||||
///
|
///
|
||||||
/// There is no alpha channel.
|
/// The RGB565 format is a common high-color representation for embedded systems:
|
||||||
/// Transparency is handled via Color Key or Blend Mode.
|
/// - **Red**: 5 bits (0..31)
|
||||||
|
/// - **Green**: 6 bits (0..63)
|
||||||
|
/// - **Blue**: 5 bits (0..31)
|
||||||
|
///
|
||||||
|
/// 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)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct Color(pub u16);
|
pub struct Color(pub u16);
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ mod cartridge;
|
|||||||
mod cartridge_loader;
|
mod cartridge_loader;
|
||||||
mod window;
|
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 button::{Button, ButtonId};
|
||||||
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
|
pub use cartridge::{AppMode, Cartridge, CartridgeDTO, CartridgeError};
|
||||||
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
|
pub use cartridge_loader::{CartridgeLoader, DirectoryCartridgeLoader, PackedCartridgeLoader};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::model::Tile;
|
|
||||||
use crate::model::tile_bank::TileSize;
|
use crate::model::tile_bank::TileSize;
|
||||||
|
use crate::model::Tile;
|
||||||
use crate::model::TileSize::Size8;
|
use crate::model::TileSize::Size8;
|
||||||
|
|
||||||
pub struct TileMap {
|
pub struct TileMap {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
mod prometeu_hub;
|
mod prometeu_hub;
|
||||||
|
mod window_manager;
|
||||||
|
|
||||||
pub use prometeu_hub::PrometeuHub;
|
pub use prometeu_hub::PrometeuHub;
|
||||||
@ -1,55 +1,9 @@
|
|||||||
use crate::hardware::HardwareBridge;
|
use crate::hardware::HardwareBridge;
|
||||||
use crate::log::{LogLevel, LogSource};
|
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;
|
use crate::prometeu_os::PrometeuOS;
|
||||||
|
|
||||||
/// PROMETEU Window Manager.
|
|
||||||
pub struct WindowManager {
|
|
||||||
pub windows: Vec<Window>,
|
|
||||||
pub focused: Option<WindowId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
/// PrometeuHub: Launcher and system UI environment.
|
||||||
pub struct PrometeuHub {
|
pub struct PrometeuHub {
|
||||||
pub window_manager: WindowManager,
|
pub window_manager: WindowManager,
|
||||||
@ -104,41 +58,3 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,4 @@
|
|||||||
mod prometeu_os;
|
mod prometeu_os;
|
||||||
pub mod syscalls;
|
|
||||||
|
|
||||||
|
use crate::virtual_machine::NativeInterface;
|
||||||
pub use prometeu_os::PrometeuOS;
|
pub use prometeu_os::PrometeuOS;
|
||||||
pub use syscalls::Syscall;
|
|
||||||
pub use crate::virtual_machine::native_interface::NativeInterface;
|
|
||||||
@ -1,62 +1,72 @@
|
|||||||
use crate::fs::{FsBackend, FsState, VirtualFS};
|
use crate::fs::{FsBackend, FsState, VirtualFS};
|
||||||
|
use crate::hardware::syscalls::Syscall;
|
||||||
use crate::hardware::{HardwareBridge, InputSignals};
|
use crate::hardware::{HardwareBridge, InputSignals};
|
||||||
use crate::log::{LogLevel, LogService, LogSource};
|
use crate::log::{LogLevel, LogService, LogSource};
|
||||||
use crate::model::{BankType, Cartridge, Color};
|
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::telemetry::{CertificationConfig, Certifier, TelemetryFrame};
|
||||||
use crate::virtual_machine::{Value, VirtualMachine};
|
use crate::virtual_machine::{Value, VirtualMachine};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Instant;
|
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.
|
/// POS is the "kernel" of the Prometeu console. It manages the lifecycle of
|
||||||
/// It acts as the bridge between the high-level PVM applications and the virtual hardware.
|
/// 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 {
|
pub struct PrometeuOS {
|
||||||
/// Incremented on every host tick (60Hz).
|
/// Host Tick Index: Incremented on every host hardware update (usually 60Hz).
|
||||||
pub tick_index: u64,
|
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,
|
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,
|
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,
|
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,
|
pub last_frame_cpu_time_us: u64,
|
||||||
|
|
||||||
// Filesystem
|
// --- Filesystem ---
|
||||||
/// The virtual filesystem interface.
|
/// The virtual filesystem interface, abstracting the physical storage.
|
||||||
pub fs: VirtualFS,
|
pub fs: VirtualFS,
|
||||||
/// Current state of the FS (Mounted, Error, etc).
|
/// Current health and connection status of the virtual filesystem.
|
||||||
pub fs_state: FsState,
|
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<u32, String>,
|
pub open_files: HashMap<u32, String>,
|
||||||
/// Sequential handle generator for file operations.
|
/// Generator for unique, non-zero file handles.
|
||||||
pub next_handle: u32,
|
pub next_handle: u32,
|
||||||
|
|
||||||
// Log Service
|
// --- Logging & Identity ---
|
||||||
|
/// The centralized service for recording and retrieving system logs.
|
||||||
pub log_service: LogService,
|
pub log_service: LogService,
|
||||||
/// Unique ID of the currently running application.
|
/// Metadata for the currently running application.
|
||||||
pub current_app_id: u32,
|
pub current_app_id: u32,
|
||||||
pub current_cartridge_title: String,
|
pub current_cartridge_title: String,
|
||||||
pub current_cartridge_app_version: String,
|
pub current_cartridge_app_version: String,
|
||||||
pub current_cartridge_app_mode: crate::model::AppMode,
|
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<u32, u32>,
|
pub logs_written_this_frame: HashMap<u32, u32>,
|
||||||
|
|
||||||
// Telemetry and Certification
|
// --- Monitoring & Debugging ---
|
||||||
/// Accumulator for metrics of the frame currently being executed.
|
/// Running counters for the current execution slice.
|
||||||
pub telemetry_current: TelemetryFrame,
|
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,
|
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,
|
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,
|
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,
|
pub debug_step_request: bool,
|
||||||
|
|
||||||
/// Instant when the system was initialized.
|
/// Wall-clock time of system startup.
|
||||||
boot_time: Instant,
|
boot_time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +207,7 @@ impl PrometeuOS {
|
|||||||
// Reset telemetry for the new logical frame
|
// Reset telemetry for the new logical frame
|
||||||
self.telemetry_current = TelemetryFrame {
|
self.telemetry_current = TelemetryFrame {
|
||||||
frame_index: self.logical_frame_index,
|
frame_index: self.logical_frame_index,
|
||||||
|
cycles_budget: self.certifier.config.cycles_budget_per_frame.unwrap_or(Self::CYCLES_PER_LOGICAL_FRAME),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -263,7 +274,7 @@ impl PrometeuOS {
|
|||||||
|
|
||||||
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
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);
|
let gfx_stats = hw.assets().bank_info(BankType::TILES);
|
||||||
self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes;
|
self.telemetry_current.gfx_used_bytes = gfx_stats.used_bytes;
|
||||||
self.telemetry_current.gfx_inflight_bytes = gfx_stats.inflight_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 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) {
|
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.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_used_bytes = self.telemetry_current.gfx_used_bytes;
|
||||||
self.telemetry_last.gfx_inflight_bytes = self.telemetry_current.gfx_inflight_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;
|
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");
|
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]
|
#[test]
|
||||||
fn test_get_color_logic() {
|
fn test_get_color_logic() {
|
||||||
let os = PrometeuOS::new(None);
|
let os = PrometeuOS::new(None);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ pub struct TelemetryFrame {
|
|||||||
pub frame_index: u64,
|
pub frame_index: u64,
|
||||||
pub vm_steps: u32,
|
pub vm_steps: u32,
|
||||||
pub cycles_used: u64,
|
pub cycles_used: u64,
|
||||||
|
pub cycles_budget: u64,
|
||||||
pub syscalls: u32,
|
pub syscalls: u32,
|
||||||
pub host_cpu_time_us: u64,
|
pub host_cpu_time_us: u64,
|
||||||
pub violations: u32,
|
pub violations: u32,
|
||||||
|
|||||||
@ -3,10 +3,20 @@ mod value;
|
|||||||
mod call_frame;
|
mod call_frame;
|
||||||
mod scope_frame;
|
mod scope_frame;
|
||||||
mod program;
|
mod program;
|
||||||
pub mod native_interface;
|
|
||||||
|
|
||||||
pub use prometeu_bytecode::opcode::OpCode;
|
use crate::hardware::HardwareBridge;
|
||||||
pub use program::Program;
|
pub use program::Program;
|
||||||
|
pub use prometeu_bytecode::opcode::OpCode;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
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<u64, String>;
|
||||||
|
}
|
||||||
|
|||||||
@ -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<u64, String>;
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
use crate::virtual_machine::Value;
|
use crate::virtual_machine::Value;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
|
|||||||
@ -1,15 +1,28 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
/// 32-bit signed integer. Used for most loop counters and indices.
|
||||||
Int32(i32),
|
Int32(i32),
|
||||||
|
/// 64-bit signed integer. Used for large numbers and timestamps.
|
||||||
Int64(i64),
|
Int64(i64),
|
||||||
|
/// 64-bit double precision float. Used for physics and complex math.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
|
/// Boolean value (true/false).
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
|
/// UTF-8 string. Strings are immutable and usually come from the Constant Pool.
|
||||||
String(String),
|
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,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
use crate::hardware::HardwareBridge;
|
use crate::hardware::HardwareBridge;
|
||||||
use crate::prometeu_os::NativeInterface;
|
|
||||||
use crate::virtual_machine::call_frame::CallFrame;
|
use crate::virtual_machine::call_frame::CallFrame;
|
||||||
use crate::virtual_machine::scope_frame::ScopeFrame;
|
use crate::virtual_machine::scope_frame::ScopeFrame;
|
||||||
use crate::virtual_machine::value::Value;
|
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::opcode::OpCode;
|
||||||
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
||||||
|
|
||||||
@ -37,28 +36,41 @@ pub struct BudgetReport {
|
|||||||
|
|
||||||
/// The PVM (PROMETEU Virtual Machine).
|
/// The PVM (PROMETEU Virtual Machine).
|
||||||
///
|
///
|
||||||
/// A deterministic, stack-based virtual machine designed for educational purposes.
|
/// A deterministic, stack-based virtual machine designed for game logic and
|
||||||
/// It models execution through fixed-cost cycles and explicit memory management.
|
/// 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 {
|
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,
|
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<Value>,
|
pub operand_stack: Vec<Value>,
|
||||||
/// Call Stack: stores execution frames for function calls.
|
/// Call Stack: Manages function call context (return addresses, frame limits).
|
||||||
pub call_stack: Vec<CallFrame>,
|
pub call_stack: Vec<CallFrame>,
|
||||||
/// Scope Stack: stores frames for blocks within a function.
|
/// Scope Stack: Handles block-level local variable visibility (scopes).
|
||||||
pub scope_stack: Vec<ScopeFrame>,
|
pub scope_stack: Vec<ScopeFrame>,
|
||||||
/// Globals: storage for persistent variables that survive between frames.
|
/// Global Variable Store: Variables that persist for the lifetime of the program.
|
||||||
pub globals: Vec<Value>,
|
pub globals: Vec<Value>,
|
||||||
/// The currently loaded program (Bytecode + Constant Pool).
|
/// The loaded executable (Bytecode + Constant Pool), that is the ROM translated.
|
||||||
pub program: Program,
|
pub program: Program,
|
||||||
/// Dynamic memory region for complex structures (Simplified in current version).
|
/// Heap Memory: Dynamic allocation pool.
|
||||||
pub heap: Vec<Value>,
|
pub heap: Vec<Value>,
|
||||||
/// Total accumulated execution cycles since the last reset.
|
/// Total virtual cycles consumed since the VM started.
|
||||||
pub cycles: u64,
|
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,
|
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<usize>,
|
pub breakpoints: std::collections::HashSet<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,7 +704,6 @@ impl VirtualMachine {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::hardware::HardwareBridge;
|
use crate::hardware::HardwareBridge;
|
||||||
use crate::prometeu_os::NativeInterface;
|
|
||||||
use crate::virtual_machine::Value;
|
use crate::virtual_machine::Value;
|
||||||
|
|
||||||
struct MockNative;
|
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)
|
// 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.
|
// 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.
|
// If they were closed, scope_stack would be empty for this frame.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_cap_config() {
|
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";
|
let path = "test_cap.cfg";
|
||||||
std::fs::write(path, content).unwrap();
|
std::fs::write(path, content).unwrap();
|
||||||
|
|
||||||
|
|||||||
@ -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 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::io::{Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
/// Host-side implementation of the PROMETEU DevTools Protocol.
|
/// Host-side implementation of the PROMETEU DevTools Protocol.
|
||||||
///
|
///
|
||||||
@ -276,6 +276,7 @@ impl HostDebugger {
|
|||||||
vm_steps: tel.vm_steps,
|
vm_steps: tel.vm_steps,
|
||||||
syscalls: tel.syscalls,
|
syscalls: tel.syscalls,
|
||||||
cycles: tel.cycles_used,
|
cycles: tel.cycles_used,
|
||||||
|
cycles_budget: tel.cycles_budget,
|
||||||
host_cpu_time_us: tel.host_cpu_time_us,
|
host_cpu_time_us: tel.host_cpu_time_us,
|
||||||
violations: tel.violations,
|
violations: tel.violations,
|
||||||
gfx_used_bytes: tel.gfx_used_bytes,
|
gfx_used_bytes: tel.gfx_used_bytes,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
use std::fs;
|
|
||||||
use prometeu_core::fs::{FsBackend, FsEntry, FsError};
|
use prometeu_core::fs::{FsBackend, FsEntry, FsError};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub struct HostDirBackend {
|
pub struct HostDirBackend {
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
@ -85,8 +85,8 @@ impl FsBackend for HostDirBackend {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
fn get_temp_dir(name: &str) -> PathBuf {
|
fn get_temp_dir(name: &str) -> PathBuf {
|
||||||
let mut path = env::temp_dir();
|
let mut path = env::temp_dir();
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
use prometeu_core::hardware::InputSignals;
|
||||||
|
use prometeu_core::Hardware;
|
||||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
use prometeu_core::hardware::InputSignals;
|
|
||||||
use prometeu_core::Hardware;
|
|
||||||
|
|
||||||
pub struct HostInputHandler {
|
pub struct HostInputHandler {
|
||||||
pub signals: InputSignals,
|
pub signals: InputSignals,
|
||||||
|
|||||||
@ -8,11 +8,11 @@ pub mod input;
|
|||||||
pub mod cap;
|
pub mod cap;
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
|
|
||||||
use runner::HostRunner;
|
|
||||||
use cap::load_cap_config;
|
use cap::load_cap_config;
|
||||||
use winit::event_loop::EventLoop;
|
|
||||||
use prometeu_core::firmware::BootTarget;
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use prometeu_core::firmware::BootTarget;
|
||||||
|
use runner::HostRunner;
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
use crate::audio::HostAudio;
|
use crate::audio::HostAudio;
|
||||||
use crate::fs_backend::HostDirBackend;
|
|
||||||
use crate::log_sink::HostConsoleSink;
|
|
||||||
use crate::debugger::HostDebugger;
|
use crate::debugger::HostDebugger;
|
||||||
use crate::stats::HostStats;
|
use crate::fs_backend::HostDirBackend;
|
||||||
use crate::input::HostInputHandler;
|
use crate::input::HostInputHandler;
|
||||||
|
use crate::log_sink::HostConsoleSink;
|
||||||
|
use crate::stats::HostStats;
|
||||||
use crate::utilities::draw_rgb565_to_rgba8;
|
use crate::utilities::draw_rgb565_to_rgba8;
|
||||||
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
|
|
||||||
use pixels::wgpu::PresentMode;
|
use pixels::wgpu::PresentMode;
|
||||||
|
use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
|
||||||
use prometeu_core::firmware::{BootTarget, Firmware};
|
use prometeu_core::firmware::{BootTarget, Firmware};
|
||||||
use prometeu_core::Hardware;
|
use prometeu_core::Hardware;
|
||||||
use std::time::{Duration, Instant};
|
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_bg = prometeu_core::model::Color::INDIGO; // Dark blue to stand out
|
||||||
let color_warn = prometeu_core::model::Color::RED;
|
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, 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, 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, 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, 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);
|
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 {
|
if tel.gfx_inflight_bytes > 0 {
|
||||||
@ -337,10 +343,10 @@ impl ApplicationHandler for HostRunner {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use prometeu_core::firmware::BootTarget;
|
|
||||||
use prometeu_core::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION;
|
use prometeu_core::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION;
|
||||||
use std::net::TcpStream;
|
use prometeu_core::firmware::BootTarget;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_debug_port_opens() {
|
fn test_debug_port_opens() {
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
{
|
{
|
||||||
"event": "telemetry",
|
"event": "telemetry",
|
||||||
"fields": [
|
"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",
|
"gfx_used_bytes", "gfx_inflight_bytes", "gfx_slots_occupied",
|
||||||
"audio_used_bytes", "audio_inflight_bytes", "audio_slots_occupied"
|
"audio_used_bytes", "audio_inflight_bytes", "audio_slots_occupied"
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user