diff --git a/Cargo.lock b/Cargo.lock index 367bed91..552dd83b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/crates/prometeu-bytecode/Cargo.toml b/crates/prometeu-bytecode/Cargo.toml new file mode 100644 index 00000000..6d00aa1b --- /dev/null +++ b/crates/prometeu-bytecode/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "prometeu-bytecode" +version = "0.1.0" +edition = "2021" + +[dependencies] +# No dependencies for now diff --git a/crates/prometeu-bytecode/README.md b/crates/prometeu-bytecode/README.md new file mode 100644 index 00000000..cc96b1b1 --- /dev/null +++ b/crates/prometeu-bytecode/README.md @@ -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); +} +``` diff --git a/crates/prometeu-bytecode/src/abi.rs b/crates/prometeu-bytecode/src/abi.rs new file mode 100644 index 00000000..3cde16d2 --- /dev/null +++ b/crates/prometeu-bytecode/src/abi.rs @@ -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 +} diff --git a/crates/prometeu-bytecode/src/asm.rs b/crates/prometeu-bytecode/src/asm.rs new file mode 100644 index 00000000..8079997f --- /dev/null +++ b/crates/prometeu-bytecode/src/asm.rs @@ -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), + Label(String), +} + +pub fn assemble(instructions: &[Asm]) -> Result, 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) +} diff --git a/crates/prometeu-bytecode/src/disasm.rs b/crates/prometeu-bytecode/src/disasm.rs new file mode 100644 index 00000000..c5e8376c --- /dev/null +++ b/crates/prometeu-bytecode/src/disasm.rs @@ -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, +} + +#[derive(Debug, Clone)] +pub enum DisasmOperand { + U32(u32), + I32(i32), + I64(i64), + F64(f64), + Bool(bool), +} + +pub fn disasm(rom: &[u8]) -> Result, String> { + let mut instructions = Vec::new(); + let mut cursor = Cursor::new(rom); + + while (cursor.position() as usize) < rom.len() { + let pc = cursor.position() as u32; + let op_val = read_u16_le(&mut cursor).map_err(|e| e.to_string())?; + let opcode = OpCode::try_from(op_val)?; + let mut operands = Vec::new(); + + match opcode { + OpCode::PushConst | OpCode::PushI32 | OpCode::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) +} diff --git a/crates/prometeu-bytecode/src/lib.rs b/crates/prometeu-bytecode/src/lib.rs new file mode 100644 index 00000000..7050a9d6 --- /dev/null +++ b/crates/prometeu-bytecode/src/lib.rs @@ -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); + } +} diff --git a/crates/prometeu-core/src/virtual_machine/opcode.rs b/crates/prometeu-bytecode/src/opcode.rs similarity index 82% rename from crates/prometeu-core/src/virtual_machine/opcode.rs rename to crates/prometeu-bytecode/src/opcode.rs index 11b4fb9b..c0e54dc4 100644 --- a/crates/prometeu-core/src/virtual_machine/opcode.rs +++ b/crates/prometeu-bytecode/src/opcode.rs @@ -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); - } -} \ No newline at end of file diff --git a/crates/prometeu-bytecode/src/pbc.rs b/crates/prometeu-bytecode/src/pbc.rs new file mode 100644 index 00000000..b69ca398 --- /dev/null +++ b/crates/prometeu-bytecode/src/pbc.rs @@ -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, + pub rom: Vec, +} + +pub fn parse_pbc(bytes: &[u8]) -> Result { + 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, 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) +} diff --git a/crates/prometeu-bytecode/src/readwrite.rs b/crates/prometeu-bytecode/src/readwrite.rs new file mode 100644 index 00000000..c52bc1a6 --- /dev/null +++ b/crates/prometeu-bytecode/src/readwrite.rs @@ -0,0 +1,41 @@ +use std::io::{self, Read, Write}; + +pub fn read_u16_le(mut reader: R) -> io::Result { + let mut buf = [0u8; 2]; + reader.read_exact(&mut buf)?; + Ok(u16::from_le_bytes(buf)) +} + +pub fn read_u32_le(mut reader: R) -> io::Result { + let mut buf = [0u8; 4]; + reader.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) +} + +pub fn read_i64_le(mut reader: R) -> io::Result { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(i64::from_le_bytes(buf)) +} + +pub fn read_f64_le(mut reader: R) -> io::Result { + let mut buf = [0u8; 8]; + reader.read_exact(&mut buf)?; + Ok(f64::from_le_bytes(buf)) +} + +pub fn write_u16_le(mut writer: W, val: u16) -> io::Result<()> { + writer.write_all(&val.to_le_bytes()) +} + +pub fn write_u32_le(mut writer: W, val: u32) -> io::Result<()> { + writer.write_all(&val.to_le_bytes()) +} + +pub fn write_i64_le(mut writer: W, val: i64) -> io::Result<()> { + writer.write_all(&val.to_le_bytes()) +} + +pub fn write_f64_le(mut writer: W, val: f64) -> io::Result<()> { + writer.write_all(&val.to_le_bytes()) +} diff --git a/crates/prometeu-core/Cargo.toml b/crates/prometeu-core/Cargo.toml index 82fa4f9f..94a0cc7d 100644 --- a/crates/prometeu-core/Cargo.toml +++ b/crates/prometeu-core/Cargo.toml @@ -6,4 +6,5 @@ license.workspace = true [dependencies] serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" \ No newline at end of file +serde_json = "1.0" +prometeu-bytecode = { path = "../prometeu-bytecode" } \ No newline at end of file diff --git a/crates/prometeu-core/src/virtual_machine/mod.rs b/crates/prometeu-core/src/virtual_machine/mod.rs index dccbfb80..bc9a6a18 100644 --- a/crates/prometeu-core/src/virtual_machine/mod.rs +++ b/crates/prometeu-core/src/virtual_machine/mod.rs @@ -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}; diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index 4d336434..fc25ced0 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -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, Vec), 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 { - 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 { - 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 { - 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 { diff --git a/crates/prometeuc/Cargo.toml b/crates/prometeuc/Cargo.toml new file mode 100644 index 00000000..7e87b37e --- /dev/null +++ b/crates/prometeuc/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "prometeuc" +version = "0.1.0" +edition = "2021" + +[dependencies] +prometeu-bytecode = { path = "../prometeu-bytecode" } diff --git a/crates/prometeuc/src/codegen/mod.rs b/crates/prometeuc/src/codegen/mod.rs new file mode 100644 index 00000000..141c9923 --- /dev/null +++ b/crates/prometeuc/src/codegen/mod.rs @@ -0,0 +1,2 @@ +pub use prometeu_bytecode::opcode::OpCode; +pub use prometeu_bytecode::asm::{Asm, Operand, assemble}; diff --git a/crates/prometeuc/src/main.rs b/crates/prometeuc/src/main.rs new file mode 100644 index 00000000..2e184d62 --- /dev/null +++ b/crates/prometeuc/src/main.rs @@ -0,0 +1,5 @@ +pub mod codegen; + +fn main() { + println!("Prometeu Compiler (stub)"); +}