dev/vm-improvements #3
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -1582,10 +1582,15 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-bytecode"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "prometeu-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"prometeu-bytecode",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
7
crates/prometeu-bytecode/Cargo.toml
Normal file
7
crates/prometeu-bytecode/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "prometeu-bytecode"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# No dependencies for now
|
||||
152
crates/prometeu-bytecode/README.md
Normal file
152
crates/prometeu-bytecode/README.md
Normal file
@ -0,0 +1,152 @@
|
||||
# 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).
|
||||
|
||||
## 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**.
|
||||
|
||||
### Convenção de Notação da Pilha
|
||||
Nas tabelas abaixo, usamos a seguinte notação para representar o estado da pilha:
|
||||
`[a, b] -> [c]`
|
||||
Significa que a instrução remove `a` e `b` da pilha (onde `b` estava no topo) e insere `c` no topo.
|
||||
|
||||
---
|
||||
|
||||
## Conjunto de Instruções (ISA)
|
||||
|
||||
### 6.1 Controle de Execução
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `Nop` | `0x00` | - | `[] -> []` | Nenhuma operação. |
|
||||
| `Halt` | `0x01` | - | `[] -> []` | Interrompe a execução da VM permanentemente. |
|
||||
| `Jmp` | `0x02` | `addr: u32` | `[] -> []` | Salto incondicional para o endereço absoluto `addr`. |
|
||||
| `JmpIfFalse`| `0x03` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `false`. |
|
||||
| `JmpIfTrue` | `0x04` | `addr: u32` | `[bool] -> []` | Salta para `addr` se o valor desempilhado for `true`. |
|
||||
| `Trap` | `0x05` | - | `[] -> []` | Interrupção para debugger (breakpoint). |
|
||||
|
||||
### 6.2 Manipulação da Pilha
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `PushConst` | `0x10` | `idx: u32` | `[] -> [val]` | Carrega a constante do índice `idx` da Constant Pool. |
|
||||
| `Pop` | `0x11` | - | `[val] -> []` | Remove e descarta o valor do topo da pilha. |
|
||||
| `Dup` | `0x12` | - | `[val] -> [val, val]` | Duplica o valor no topo da pilha. |
|
||||
| `Swap` | `0x13` | - | `[a, b] -> [b, a]` | Inverte a posição dos dois valores no topo. |
|
||||
| `PushI64` | `0x14` | `val: i64` | `[] -> [i64]` | Empilha um inteiro de 64 bits imediato. |
|
||||
| `PushF64` | `0x15` | `val: f64` | `[] -> [f64]` | Empilha um ponto flutuante de 64 bits imediato. |
|
||||
| `PushBool` | `0x16` | `val: u8` | `[] -> [bool]` | Empilha um booleano (0=false, 1=true). |
|
||||
| `PushI32` | `0x17` | `val: i32` | `[] -> [i32]` | Empilha um inteiro de 32 bits imediato. |
|
||||
| `PopN` | `0x18` | `n: u16` | `[...] -> [...]` | Remove `n` valores da pilha de uma vez. |
|
||||
|
||||
### 6.3 Aritmética
|
||||
A VM realiza promoção automática de tipos (ex: `i32` + `f64` resulta em `f64`).
|
||||
|
||||
| OpCode | Valor | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `Add` | `0x20` | `[a, b] -> [a + b]` | Soma os dois valores do topo. |
|
||||
| `Sub` | `0x21` | `[a, b] -> [a - b]` | Subtrai `b` de `a`. |
|
||||
| `Mul` | `0x22` | `[a, b] -> [a * b]` | Multiplica os dois valores do topo. |
|
||||
| `Div` | `0x23` | `[a, b] -> [a / b]` | Divide `a` por `b`. Erro se `b == 0`. |
|
||||
| `Neg` | `0x3E` | `[a] -> [-a]` | Inverte o sinal numérico. |
|
||||
|
||||
### 6.4 Lógica e Comparação
|
||||
|
||||
| OpCode | Valor | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `Eq` | `0x30` | `[a, b] -> [bool]` | Testa igualdade. |
|
||||
| `Neq` | `0x31` | `[a, b] -> [bool]` | Testa desigualdade. |
|
||||
| `Lt` | `0x32` | `[a, b] -> [bool]` | `a < b` |
|
||||
| `Gt` | `0x33` | `[a, b] -> [bool]` | `a > b` |
|
||||
| `Lte` | `0x3C` | `[a, b] -> [bool]` | `a <= b` |
|
||||
| `Gte` | `0x3D` | `[a, b] -> [bool]` | `a >= b` |
|
||||
| `And` | `0x34` | `[a, b] -> [bool]` | AND lógico (booleano). |
|
||||
| `Or` | `0x35` | `[a, b] -> [bool]` | OR lógico (booleano). |
|
||||
| `Not` | `0x36` | `[a] -> [!a]` | NOT lógico. |
|
||||
| `BitAnd` | `0x37` | `[a, b] -> [int]` | AND bit a bit. |
|
||||
| `BitOr` | `0x38` | `[a, b] -> [int]` | OR bit a bit. |
|
||||
| `BitXor` | `0x39` | `[a, b] -> [int]` | XOR bit a bit. |
|
||||
| `Shl` | `0x3A` | `[a, b] -> [int]` | Shift Left: `a << b`. |
|
||||
| `Shr` | `0x3B` | `[a, b] -> [int]` | Shift Right: `a >> b`. |
|
||||
|
||||
### 6.5 Variáveis e Memória
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `GetGlobal`| `0x40` | `idx: u32` | `[] -> [val]` | Carrega valor da global `idx`. |
|
||||
| `SetGlobal`| `0x41` | `idx: u32` | `[val] -> []` | Armazena topo na global `idx`. |
|
||||
| `GetLocal` | `0x42` | `idx: u32` | `[] -> [val]` | Carrega local `idx` do frame atual. |
|
||||
| `SetLocal` | `0x43` | `idx: u32` | `[val] -> []` | Armazena topo na local `idx` do frame atual. |
|
||||
|
||||
### 6.6 Funções e Escopo
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `Call` | `0x50` | `addr: u32, args: u32` | `[a1, a2] -> [...]` | Chama `addr`. Os `args` valores no topo viram locais do novo frame. |
|
||||
| `Ret` | `0x51` | - | `[val] -> [val]` | Retorna da função atual, limpando o frame e devolvendo o valor do topo. |
|
||||
| `PushScope`| `0x52` | - | `[] -> []` | Inicia um sub-escopo (bloco) para locais temporários. |
|
||||
| `PopScope` | `0x53` | - | `[] -> []` | Finaliza sub-escopo, removendo locais criados nele da pilha. |
|
||||
|
||||
### 6.7 Heap (Memória Dinâmica)
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `Alloc` | `0x60` | `size: u32` | `[] -> [ref]` | Aloca `size` slots no heap e retorna uma referência. |
|
||||
| `LoadRef` | `0x61` | `offset: u32`| `[ref] -> [val]` | Lê valor do heap no endereço `ref + offset`. |
|
||||
| `StoreRef`| `0x62` | `offset: u32`| `[ref, val] -> []` | Escreve `val` no heap no endereço `ref + offset`. |
|
||||
|
||||
### 6.8 Sistema e Sincronização
|
||||
|
||||
| OpCode | Valor | Operandos | Pilha | Descrição |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `Syscall` | `0x70` | `id: u32` | `[...] -> [...]` | Invoca uma função do sistema/firmware. A pilha depende da syscall. |
|
||||
| `FrameSync`| `0x80` | - | `[] -> []` | Marca o fim do processamento do frame lógico atual (60 FPS). |
|
||||
|
||||
---
|
||||
|
||||
## Estrutura do PBC (Prometeu ByteCode)
|
||||
|
||||
O PBC é o formato binário oficial para programas Prometeu.
|
||||
|
||||
```rust
|
||||
// Exemplo de como carregar um arquivo PBC
|
||||
let bytes = std::fs::read("game.pbc")?;
|
||||
let pbc = prometeu_bytecode::pbc::parse_pbc(&bytes)?;
|
||||
|
||||
println!("ROM size: {} bytes", pbc.rom.len());
|
||||
println!("Constants: {}", pbc.cp.len());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Assembler e Disassembler
|
||||
|
||||
Este crate fornece ferramentas para facilitar a geração e inspeção de código.
|
||||
|
||||
### Montagem (Assembler)
|
||||
```rust
|
||||
use prometeu_bytecode::asm::{assemble, Asm, Operand};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
|
||||
let instructions = vec![
|
||||
Asm::Op(OpCode::PushI32, vec![Operand::I32(10)]),
|
||||
Asm::Op(OpCode::PushI32, vec![Operand::I32(20)]),
|
||||
Asm::Op(OpCode::Add, vec![]),
|
||||
Asm::Op(OpCode::Halt, vec![]),
|
||||
];
|
||||
|
||||
let rom_bytes = assemble(&instructions).unwrap();
|
||||
```
|
||||
|
||||
### Desmontagem (Disassembler)
|
||||
```rust
|
||||
use prometeu_bytecode::disasm::disasm;
|
||||
|
||||
let rom = vec![/* ... bytes ... */];
|
||||
let code = disasm(&rom);
|
||||
|
||||
for instr in code {
|
||||
println!("{:04X}: {:?} {:?}", instr.pc, instr.opcode, instr.operands);
|
||||
}
|
||||
```
|
||||
29
crates/prometeu-bytecode/src/abi.rs
Normal file
29
crates/prometeu-bytecode/src/abi.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::opcode::OpCode;
|
||||
|
||||
pub fn operand_size(opcode: OpCode) -> usize {
|
||||
match opcode {
|
||||
OpCode::PushConst => 4,
|
||||
OpCode::PushI32 => 4,
|
||||
OpCode::PushI64 => 8,
|
||||
OpCode::PushF64 => 8,
|
||||
OpCode::PushBool => 1,
|
||||
OpCode::PopN => 4,
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => 4,
|
||||
OpCode::GetGlobal | OpCode::SetGlobal => 4,
|
||||
OpCode::GetLocal | OpCode::SetLocal => 4,
|
||||
OpCode::Call => 8, // addr(u32) + args_count(u32)
|
||||
OpCode::Syscall => 4,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_jump(opcode: OpCode) -> bool {
|
||||
match opcode {
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_immediate(opcode: OpCode) -> bool {
|
||||
operand_size(opcode) > 0
|
||||
}
|
||||
69
crates/prometeu-bytecode/src/asm.rs
Normal file
69
crates/prometeu-bytecode/src/asm.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use crate::opcode::OpCode;
|
||||
use crate::readwrite::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Operand {
|
||||
U32(u32),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F64(f64),
|
||||
Bool(bool),
|
||||
Label(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Asm {
|
||||
Op(OpCode, Vec<Operand>),
|
||||
Label(String),
|
||||
}
|
||||
|
||||
pub fn assemble(instructions: &[Asm]) -> Result<Vec<u8>, String> {
|
||||
let mut labels = HashMap::new();
|
||||
let mut current_pc = 0u32;
|
||||
|
||||
// First pass: resolve labels
|
||||
for instr in instructions {
|
||||
match instr {
|
||||
Asm::Label(name) => {
|
||||
labels.insert(name.clone(), current_pc);
|
||||
}
|
||||
Asm::Op(_opcode, operands) => {
|
||||
current_pc += 2; // OpCode is u16 (2 bytes)
|
||||
for operand in operands {
|
||||
match operand {
|
||||
Operand::U32(_) | Operand::I32(_) | Operand::Label(_) => current_pc += 4,
|
||||
Operand::I64(_) | Operand::F64(_) => current_pc += 8,
|
||||
Operand::Bool(_) => current_pc += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: generate bytes
|
||||
let mut rom = Vec::new();
|
||||
for instr in instructions {
|
||||
match instr {
|
||||
Asm::Label(_) => {}
|
||||
Asm::Op(opcode, operands) => {
|
||||
write_u16_le(&mut rom, *opcode as u16).map_err(|e| e.to_string())?;
|
||||
for operand in operands {
|
||||
match operand {
|
||||
Operand::U32(v) => write_u32_le(&mut rom, *v).map_err(|e| e.to_string())?,
|
||||
Operand::I32(v) => write_u32_le(&mut rom, *v as u32).map_err(|e| e.to_string())?,
|
||||
Operand::I64(v) => write_i64_le(&mut rom, *v).map_err(|e| e.to_string())?,
|
||||
Operand::F64(v) => write_f64_le(&mut rom, *v).map_err(|e| e.to_string())?,
|
||||
Operand::Bool(v) => rom.push(if *v { 1 } else { 0 }),
|
||||
Operand::Label(name) => {
|
||||
let addr = labels.get(name).ok_or(format!("Undefined label: {}", name))?;
|
||||
write_u32_le(&mut rom, *addr).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rom)
|
||||
}
|
||||
64
crates/prometeu-bytecode/src/disasm.rs
Normal file
64
crates/prometeu-bytecode/src/disasm.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::opcode::OpCode;
|
||||
use crate::readwrite::*;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Instr {
|
||||
pub pc: u32,
|
||||
pub opcode: OpCode,
|
||||
pub operands: Vec<DisasmOperand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DisasmOperand {
|
||||
U32(u32),
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F64(f64),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
pub fn disasm(rom: &[u8]) -> Result<Vec<Instr>, String> {
|
||||
let mut instructions = Vec::new();
|
||||
let mut cursor = Cursor::new(rom);
|
||||
|
||||
while (cursor.position() as usize) < rom.len() {
|
||||
let pc = cursor.position() as u32;
|
||||
let op_val = read_u16_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
let opcode = OpCode::try_from(op_val)?;
|
||||
let mut operands = Vec::new();
|
||||
|
||||
match opcode {
|
||||
OpCode::PushConst | OpCode::PushI32 | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue
|
||||
| OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal
|
||||
| OpCode::PopN | OpCode::Syscall => {
|
||||
let v = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
operands.push(DisasmOperand::U32(v));
|
||||
}
|
||||
OpCode::PushI64 => {
|
||||
let v = read_i64_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
operands.push(DisasmOperand::I64(v));
|
||||
}
|
||||
OpCode::PushF64 => {
|
||||
let v = read_f64_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
operands.push(DisasmOperand::F64(v));
|
||||
}
|
||||
OpCode::PushBool => {
|
||||
let mut b_buf = [0u8; 1];
|
||||
cursor.read_exact(&mut b_buf).map_err(|e| e.to_string())?;
|
||||
operands.push(DisasmOperand::Bool(b_buf[0] != 0));
|
||||
}
|
||||
OpCode::Call => {
|
||||
let addr = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
let args = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
operands.push(DisasmOperand::U32(addr));
|
||||
operands.push(DisasmOperand::U32(args));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
instructions.push(Instr { pc, opcode, operands });
|
||||
}
|
||||
|
||||
Ok(instructions)
|
||||
}
|
||||
51
crates/prometeu-bytecode/src/lib.rs
Normal file
51
crates/prometeu-bytecode/src/lib.rs
Normal file
@ -0,0 +1,51 @@
|
||||
pub mod opcode;
|
||||
pub mod abi;
|
||||
pub mod pbc;
|
||||
pub mod readwrite;
|
||||
pub mod asm;
|
||||
pub mod disasm;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::opcode::OpCode;
|
||||
use crate::asm::{self, Asm, Operand};
|
||||
use crate::pbc::{self, PbcFile, ConstantPoolEntry};
|
||||
use crate::disasm;
|
||||
|
||||
#[test]
|
||||
fn test_golden_abi_roundtrip() {
|
||||
// 1. Create a simple assembly program: PushI32 42; Halt
|
||||
let instructions = vec![
|
||||
Asm::Op(OpCode::PushI32, vec![Operand::I32(42)]),
|
||||
Asm::Op(OpCode::Halt, vec![]),
|
||||
];
|
||||
|
||||
let rom = asm::assemble(&instructions).expect("Failed to assemble");
|
||||
|
||||
// 2. Create a PBC file
|
||||
let pbc_file = PbcFile {
|
||||
cp: vec![ConstantPoolEntry::Int32(100)], // Random CP entry
|
||||
rom,
|
||||
};
|
||||
|
||||
let bytes = pbc::write_pbc(&pbc_file).expect("Failed to write PBC");
|
||||
|
||||
// 3. Parse it back
|
||||
let parsed_pbc = pbc::parse_pbc(&bytes).expect("Failed to parse PBC");
|
||||
|
||||
assert_eq!(parsed_pbc.cp, pbc_file.cp);
|
||||
assert_eq!(parsed_pbc.rom, pbc_file.rom);
|
||||
|
||||
// 4. Disassemble ROM
|
||||
let dis_instrs = disasm::disasm(&parsed_pbc.rom).expect("Failed to disassemble");
|
||||
|
||||
assert_eq!(dis_instrs.len(), 2);
|
||||
assert_eq!(dis_instrs[0].opcode, OpCode::PushI32);
|
||||
if let disasm::DisasmOperand::U32(v) = dis_instrs[0].operands[0] {
|
||||
assert_eq!(v, 42);
|
||||
} else {
|
||||
panic!("Wrong operand type");
|
||||
}
|
||||
assert_eq!(dis_instrs[1].opcode, OpCode::Halt);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u16)]
|
||||
pub enum OpCode {
|
||||
@ -171,34 +170,8 @@ impl OpCode {
|
||||
OpCode::Alloc => 10,
|
||||
OpCode::LoadRef => 3,
|
||||
OpCode::StoreRef => 3,
|
||||
OpCode::Syscall => 1, // Variable, but we'll use 1 as base or define via ID
|
||||
OpCode::Syscall => 1,
|
||||
OpCode::FrameSync => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_opcode_decoding() {
|
||||
assert_eq!(OpCode::try_from(0x00).unwrap(), OpCode::Nop);
|
||||
assert_eq!(OpCode::try_from(0x05).unwrap(), OpCode::Trap);
|
||||
assert_eq!(OpCode::try_from(0x10).unwrap(), OpCode::PushConst);
|
||||
assert_eq!(OpCode::try_from(0x18).unwrap(), OpCode::PopN);
|
||||
assert_eq!(OpCode::try_from(0x20).unwrap(), OpCode::Add);
|
||||
assert_eq!(OpCode::try_from(0x70).unwrap(), OpCode::Syscall);
|
||||
assert_eq!(OpCode::try_from(0x80).unwrap(), OpCode::FrameSync);
|
||||
assert!(OpCode::try_from(0xFFFF).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_opcode_cycles() {
|
||||
assert_eq!(OpCode::Nop.cycles(), 1);
|
||||
assert_eq!(OpCode::Add.cycles(), 2);
|
||||
assert_eq!(OpCode::Mul.cycles(), 4);
|
||||
assert_eq!(OpCode::Div.cycles(), 6);
|
||||
assert_eq!(OpCode::Alloc.cycles(), 10);
|
||||
}
|
||||
}
|
||||
111
crates/prometeu-bytecode/src/pbc.rs
Normal file
111
crates/prometeu-bytecode/src/pbc.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use crate::readwrite::*;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConstantPoolEntry {
|
||||
Null,
|
||||
Int64(i64),
|
||||
Float64(f64),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
Int32(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PbcFile {
|
||||
pub cp: Vec<ConstantPoolEntry>,
|
||||
pub rom: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_pbc(bytes: &[u8]) -> Result<PbcFile, String> {
|
||||
if bytes.len() < 4 || &bytes[0..4] != b"PPBC" {
|
||||
return Err("Invalid PBC signature".into());
|
||||
}
|
||||
|
||||
let mut cursor = Cursor::new(&bytes[4..]);
|
||||
|
||||
let cp_count = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as usize;
|
||||
let mut cp = Vec::with_capacity(cp_count);
|
||||
|
||||
for _ in 0..cp_count {
|
||||
let mut tag_buf = [0u8; 1];
|
||||
cursor.read_exact(&mut tag_buf).map_err(|e| e.to_string())?;
|
||||
let tag = tag_buf[0];
|
||||
|
||||
match tag {
|
||||
1 => {
|
||||
let val = read_i64_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
cp.push(ConstantPoolEntry::Int64(val));
|
||||
}
|
||||
2 => {
|
||||
let val = read_f64_le(&mut cursor).map_err(|e| e.to_string())?;
|
||||
cp.push(ConstantPoolEntry::Float64(val));
|
||||
}
|
||||
3 => {
|
||||
let mut bool_buf = [0u8; 1];
|
||||
cursor.read_exact(&mut bool_buf).map_err(|e| e.to_string())?;
|
||||
cp.push(ConstantPoolEntry::Boolean(bool_buf[0] != 0));
|
||||
}
|
||||
4 => {
|
||||
let len = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as usize;
|
||||
let mut s_buf = vec![0u8; len];
|
||||
cursor.read_exact(&mut s_buf).map_err(|e| e.to_string())?;
|
||||
let s = String::from_utf8_lossy(&s_buf).into_owned();
|
||||
cp.push(ConstantPoolEntry::String(s));
|
||||
}
|
||||
5 => {
|
||||
let val = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as i32;
|
||||
cp.push(ConstantPoolEntry::Int32(val));
|
||||
}
|
||||
_ => cp.push(ConstantPoolEntry::Null),
|
||||
}
|
||||
}
|
||||
|
||||
let rom_size = read_u32_le(&mut cursor).map_err(|e| e.to_string())? as usize;
|
||||
let mut rom = vec![0u8; rom_size];
|
||||
cursor.read_exact(&mut rom).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(PbcFile { cp, rom })
|
||||
}
|
||||
|
||||
pub fn write_pbc(pbc: &PbcFile) -> Result<Vec<u8>, String> {
|
||||
let mut out = Vec::new();
|
||||
out.write_all(b"PPBC").map_err(|e| e.to_string())?;
|
||||
|
||||
write_u32_le(&mut out, pbc.cp.len() as u32).map_err(|e| e.to_string())?;
|
||||
|
||||
for entry in &pbc.cp {
|
||||
match entry {
|
||||
ConstantPoolEntry::Null => {
|
||||
out.write_all(&[0]).map_err(|e| e.to_string())?;
|
||||
}
|
||||
ConstantPoolEntry::Int64(v) => {
|
||||
out.write_all(&[1]).map_err(|e| e.to_string())?;
|
||||
write_i64_le(&mut out, *v).map_err(|e| e.to_string())?;
|
||||
}
|
||||
ConstantPoolEntry::Float64(v) => {
|
||||
out.write_all(&[2]).map_err(|e| e.to_string())?;
|
||||
write_f64_le(&mut out, *v).map_err(|e| e.to_string())?;
|
||||
}
|
||||
ConstantPoolEntry::Boolean(v) => {
|
||||
out.write_all(&[3]).map_err(|e| e.to_string())?;
|
||||
out.write_all(&[if *v { 1 } else { 0 }]).map_err(|e| e.to_string())?;
|
||||
}
|
||||
ConstantPoolEntry::String(v) => {
|
||||
out.write_all(&[4]).map_err(|e| e.to_string())?;
|
||||
let bytes = v.as_bytes();
|
||||
write_u32_le(&mut out, bytes.len() as u32).map_err(|e| e.to_string())?;
|
||||
out.write_all(bytes).map_err(|e| e.to_string())?;
|
||||
}
|
||||
ConstantPoolEntry::Int32(v) => {
|
||||
out.write_all(&[5]).map_err(|e| e.to_string())?;
|
||||
write_u32_le(&mut out, *v as u32).map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write_u32_le(&mut out, pbc.rom.len() as u32).map_err(|e| e.to_string())?;
|
||||
out.write_all(&pbc.rom).map_err(|e| e.to_string())?;
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
41
crates/prometeu-bytecode/src/readwrite.rs
Normal file
41
crates/prometeu-bytecode/src/readwrite.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
pub fn read_u16_le<R: Read>(mut reader: R) -> io::Result<u16> {
|
||||
let mut buf = [0u8; 2];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(u16::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn read_u32_le<R: Read>(mut reader: R) -> io::Result<u32> {
|
||||
let mut buf = [0u8; 4];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(u32::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn read_i64_le<R: Read>(mut reader: R) -> io::Result<i64> {
|
||||
let mut buf = [0u8; 8];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(i64::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn read_f64_le<R: Read>(mut reader: R) -> io::Result<f64> {
|
||||
let mut buf = [0u8; 8];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(f64::from_le_bytes(buf))
|
||||
}
|
||||
|
||||
pub fn write_u16_le<W: Write>(mut writer: W, val: u16) -> io::Result<()> {
|
||||
writer.write_all(&val.to_le_bytes())
|
||||
}
|
||||
|
||||
pub fn write_u32_le<W: Write>(mut writer: W, val: u32) -> io::Result<()> {
|
||||
writer.write_all(&val.to_le_bytes())
|
||||
}
|
||||
|
||||
pub fn write_i64_le<W: Write>(mut writer: W, val: i64) -> io::Result<()> {
|
||||
writer.write_all(&val.to_le_bytes())
|
||||
}
|
||||
|
||||
pub fn write_f64_le<W: Write>(mut writer: W, val: f64) -> io::Result<()> {
|
||||
writer.write_all(&val.to_le_bytes())
|
||||
}
|
||||
@ -6,4 +6,5 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_json = "1.0"
|
||||
prometeu-bytecode = { path = "../prometeu-bytecode" }
|
||||
@ -1,12 +1,11 @@
|
||||
mod virtual_machine;
|
||||
mod value;
|
||||
mod opcode;
|
||||
mod call_frame;
|
||||
mod scope_frame;
|
||||
mod program;
|
||||
pub mod native_interface;
|
||||
|
||||
pub use opcode::OpCode;
|
||||
pub use prometeu_bytecode::opcode::OpCode;
|
||||
pub use program::Program;
|
||||
pub use value::Value;
|
||||
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};
|
||||
|
||||
@ -2,9 +2,10 @@ use crate::hardware::HardwareBridge;
|
||||
use crate::prometeu_os::NativeInterface;
|
||||
use crate::virtual_machine::call_frame::CallFrame;
|
||||
use crate::virtual_machine::scope_frame::ScopeFrame;
|
||||
use crate::virtual_machine::opcode::OpCode;
|
||||
use crate::virtual_machine::value::Value;
|
||||
use crate::virtual_machine::Program;
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::pbc::{self, ConstantPoolEntry};
|
||||
|
||||
/// Reason why the Virtual Machine stopped execution during a specific run.
|
||||
/// This allows the system to decide if it should continue execution in the next tick
|
||||
@ -84,8 +85,16 @@ impl VirtualMachine {
|
||||
// PBC (Prometeu ByteCode) is a binary format that includes a header,
|
||||
// constant pool, and the raw ROM (bytecode).
|
||||
if program_bytes.starts_with(b"PPBC") {
|
||||
if let Ok((rom, cp)) = self.parse_pbc(&program_bytes) {
|
||||
self.program = Program::new(rom, cp);
|
||||
if let Ok(pbc_file) = pbc::parse_pbc(&program_bytes) {
|
||||
let cp = pbc_file.cp.into_iter().map(|entry| match entry {
|
||||
ConstantPoolEntry::Int32(v) => Value::Int32(v),
|
||||
ConstantPoolEntry::Int64(v) => Value::Int64(v),
|
||||
ConstantPoolEntry::Float64(v) => Value::Float(v),
|
||||
ConstantPoolEntry::Boolean(v) => Value::Boolean(v),
|
||||
ConstantPoolEntry::String(v) => Value::String(v),
|
||||
ConstantPoolEntry::Null => Value::Null,
|
||||
}).collect();
|
||||
self.program = Program::new(pbc_file.rom, cp);
|
||||
} else {
|
||||
// Fallback for raw bytes if PBC parsing fails
|
||||
self.program = Program::new(program_bytes, vec![]);
|
||||
@ -111,81 +120,6 @@ impl VirtualMachine {
|
||||
self.cycles = 0;
|
||||
self.halted = false;
|
||||
}
|
||||
|
||||
/// Parses the PROMETEU binary format.
|
||||
/// Format: "PPBC" (4) | CP_COUNT (u32) | CP_ENTRIES[...] | ROM_SIZE (u32) | ROM_BYTES[...]
|
||||
fn parse_pbc(&self, bytes: &[u8]) -> Result<(Vec<u8>, Vec<Value>), String> {
|
||||
let mut cursor = 4; // Skip "PPBC" signature
|
||||
|
||||
// 1. Parse Constant Pool (literals like strings, ints, floats used in the program)
|
||||
let cp_count = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||
let mut cp = Vec::with_capacity(cp_count);
|
||||
|
||||
for _ in 0..cp_count {
|
||||
if cursor >= bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let tag = bytes[cursor];
|
||||
cursor += 1;
|
||||
|
||||
match tag {
|
||||
1 => { // Int64 (64-bit)
|
||||
let val = self.read_i64_at(bytes, &mut cursor)?;
|
||||
cp.push(Value::Int64(val));
|
||||
}
|
||||
2 => { // Float (64-bit)
|
||||
let val = self.read_f64_at(bytes, &mut cursor)?;
|
||||
cp.push(Value::Float(val));
|
||||
}
|
||||
3 => { // Boolean
|
||||
if cursor >= bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let val = bytes[cursor] != 0;
|
||||
cursor += 1;
|
||||
cp.push(Value::Boolean(val));
|
||||
}
|
||||
4 => { // String (UTF-8)
|
||||
let len = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||
if cursor + len > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let s = String::from_utf8_lossy(&bytes[cursor..cursor + len]).into_owned();
|
||||
cursor += len;
|
||||
cp.push(Value::String(s));
|
||||
}
|
||||
5 => { // Int32 (32-bit)
|
||||
let val = self.read_u32_at(bytes, &mut cursor)? as i32;
|
||||
cp.push(Value::Int32(val));
|
||||
}
|
||||
_ => cp.push(Value::Null),
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Parse ROM (executable bytecode)
|
||||
let rom_size = self.read_u32_at(bytes, &mut cursor)? as usize;
|
||||
if cursor + rom_size > bytes.len() {
|
||||
return Err("Invalid ROM size in PBC".into());
|
||||
}
|
||||
let rom = bytes[cursor..cursor + rom_size].to_vec();
|
||||
|
||||
Ok((rom, cp))
|
||||
}
|
||||
|
||||
fn read_u32_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<u32, String> {
|
||||
if *cursor + 4 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let val = u32::from_le_bytes(bytes[*cursor..*cursor + 4].try_into().unwrap());
|
||||
*cursor += 4;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn read_i64_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<i64, String> {
|
||||
if *cursor + 8 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let val = i64::from_le_bytes(bytes[*cursor..*cursor + 8].try_into().unwrap());
|
||||
*cursor += 8;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
fn read_f64_at(&self, bytes: &[u8], cursor: &mut usize) -> Result<f64, String> {
|
||||
if *cursor + 8 > bytes.len() { return Err("Unexpected end of PBC".into()); }
|
||||
let val = f64::from_le_bytes(bytes[*cursor..*cursor + 8].try_into().unwrap());
|
||||
*cursor += 8;
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VirtualMachine {
|
||||
|
||||
7
crates/prometeuc/Cargo.toml
Normal file
7
crates/prometeuc/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "prometeuc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
prometeu-bytecode = { path = "../prometeu-bytecode" }
|
||||
2
crates/prometeuc/src/codegen/mod.rs
Normal file
2
crates/prometeuc/src/codegen/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub use prometeu_bytecode::opcode::OpCode;
|
||||
pub use prometeu_bytecode::asm::{Asm, Operand, assemble};
|
||||
5
crates/prometeuc/src/main.rs
Normal file
5
crates/prometeuc/src/main.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod codegen;
|
||||
|
||||
fn main() {
|
||||
println!("Prometeu Compiler (stub)");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user