dev/vm-improvements #3

Merged
bquarkz merged 7 commits from dev/vm-improvements into master 2026-01-20 10:18:45 +00:00
16 changed files with 559 additions and 109 deletions
Showing only changes of commit 84d57897b1 - Show all commits

5
Cargo.lock generated
View File

@ -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",
]

View File

@ -0,0 +1,7 @@
[package]
name = "prometeu-bytecode"
version = "0.1.0"
edition = "2021"
[dependencies]
# No dependencies for now

View 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);
}
```

View 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
}

View 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)
}

View 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)
}

View 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);
}
}

View File

@ -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);
}
}

View 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)
}

View 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())
}

View File

@ -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" }

View File

@ -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};

View File

@ -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 {

View File

@ -0,0 +1,7 @@
[package]
name = "prometeuc"
version = "0.1.0"
edition = "2021"
[dependencies]
prometeu-bytecode = { path = "../prometeu-bytecode" }

View File

@ -0,0 +1,2 @@
pub use prometeu_bytecode::opcode::OpCode;
pub use prometeu_bytecode::asm::{Asm, Operand, assemble};

View File

@ -0,0 +1,5 @@
pub mod codegen;
fn main() {
println!("Prometeu Compiler (stub)");
}