From 845fc36bd23386acb9c88d6fef2568ab9947e7c3 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Tue, 27 Jan 2026 13:29:38 +0000 Subject: [PATCH] pr 03 --- .../src/backend/emit_bytecode.rs | 29 ++++++++----- crates/prometeu-compiler/src/ir/instr.rs | 11 ++--- crates/prometeu-compiler/src/ir/module.rs | 6 ++- .../src/ir_core/const_pool.rs | 40 +++++++++++++++++ crates/prometeu-compiler/src/ir_core/mod.rs | 2 + .../prometeu-compiler/src/ir_core/program.rs | 4 +- .../prometeu-compiler/tests/backend_tests.rs | 42 ++++++++++++++++++ .../tests/const_pool_tests.rs | 43 +++++++++++++++++++ .../prometeu-compiler/tests/ir_core_tests.rs | 11 +++++ 9 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 crates/prometeu-compiler/src/ir_core/const_pool.rs create mode 100644 crates/prometeu-compiler/tests/backend_tests.rs create mode 100644 crates/prometeu-compiler/tests/const_pool_tests.rs diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index 7d109ada..93686dc2 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -11,6 +11,7 @@ use crate::common::files::FileManager; use crate::common::symbols::Symbol; use crate::ir; use crate::ir::instr::InstrKind; +use crate::ir_core::ConstantValue; use anyhow::{anyhow, Result}; use prometeu_bytecode::asm::{assemble, update_pc_by_operand, Asm, Operand}; use prometeu_bytecode::opcode::OpCode; @@ -58,11 +59,26 @@ impl<'a> BytecodeEmitter<'a> { } } + fn add_ir_constant(&mut self, val: &ConstantValue) -> u32 { + let entry = match val { + ConstantValue::Int(v) => ConstantPoolEntry::Int64(*v), + ConstantValue::Float(v) => ConstantPoolEntry::Float64(*v), + ConstantValue::String(s) => ConstantPoolEntry::String(s.clone()), + }; + self.add_constant(entry) + } + /// Transforms an IR module into a binary PBC file. fn emit(&mut self, module: &ir::Module) -> Result { let mut asm_instrs = Vec::new(); let mut ir_instr_map = Vec::new(); // Maps Asm index to IR instruction (for symbols) + // Pre-populate constant pool from IR and create a mapping for ConstIds + let mut mapped_const_ids = Vec::with_capacity(module.const_pool.constants.len()); + for val in &module.const_pool.constants { + mapped_const_ids.push(self.add_ir_constant(val)); + } + // --- PHASE 1: Lowering IR to Assembly-like structures --- for function in &module.functions { // Each function starts with a label for its entry point. @@ -77,20 +93,13 @@ impl<'a> BytecodeEmitter<'a> { match &instr.kind { InstrKind::Nop => asm_instrs.push(Asm::Op(OpCode::Nop, vec![])), InstrKind::Halt => asm_instrs.push(Asm::Op(OpCode::Halt, vec![])), - InstrKind::PushInt(v) => { - asm_instrs.push(Asm::Op(OpCode::PushI64, vec![Operand::I64(*v)])); - } - InstrKind::PushFloat(v) => { - asm_instrs.push(Asm::Op(OpCode::PushF64, vec![Operand::F64(*v)])); + InstrKind::PushConst(id) => { + let mapped_id = mapped_const_ids[id.0 as usize]; + asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(mapped_id)])); } InstrKind::PushBool(v) => { asm_instrs.push(Asm::Op(OpCode::PushBool, vec![Operand::Bool(*v)])); } - InstrKind::PushString(s) => { - // Strings are stored in the constant pool. - let id = self.add_constant(ConstantPoolEntry::String(s.clone())); - asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(id)])); - } InstrKind::PushNull => { asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(0)])); } diff --git a/crates/prometeu-compiler/src/ir/instr.rs b/crates/prometeu-compiler/src/ir/instr.rs index 2b2b4a61..1c16d592 100644 --- a/crates/prometeu-compiler/src/ir/instr.rs +++ b/crates/prometeu-compiler/src/ir/instr.rs @@ -5,6 +5,7 @@ //! easy to lower into VM-specific bytecode. use crate::common::spans::Span; +use crate::ir_core::ids::ConstId; /// An `Instruction` combines an instruction's behavior (`kind`) with its /// source code location (`span`) for debugging and error reporting. @@ -38,16 +39,12 @@ pub enum InstrKind { Halt, // --- Literals --- - // These instructions push a constant value onto the stack. + // These instructions push a constant value from the pool onto the stack. - /// Pushes a 64-bit integer onto the stack. - PushInt(i64), - /// Pushes a 64-bit float onto the stack. - PushFloat(f64), + /// Pushes a constant from the pool onto the stack. + PushConst(ConstId), /// Pushes a boolean onto the stack. PushBool(bool), - /// Pushes a string literal onto the stack. - PushString(String), /// Pushes a `null` value onto the stack. PushNull, diff --git a/crates/prometeu-compiler/src/ir/module.rs b/crates/prometeu-compiler/src/ir/module.rs index 2784b078..0ab6e2e9 100644 --- a/crates/prometeu-compiler/src/ir/module.rs +++ b/crates/prometeu-compiler/src/ir/module.rs @@ -6,13 +6,16 @@ use crate::ir::instr::Instruction; use crate::ir::types::Type; +use crate::ir_core::const_pool::ConstPool; /// A `Module` is the top-level container for a compiled program or library. -/// It contains a collection of global variables and functions. +/// It contains a collection of global variables, functions, and a constant pool. #[derive(Debug, Clone)] pub struct Module { /// The name of the module (usually derived from the project name). pub name: String, + /// Shared constant pool for this module. + pub const_pool: ConstPool, /// List of all functions defined in this module. pub functions: Vec, /// List of all global variables available in this module. @@ -60,6 +63,7 @@ impl Module { pub fn new(name: String) -> Self { Self { name, + const_pool: ConstPool::new(), functions: Vec::new(), globals: Vec::new(), } diff --git a/crates/prometeu-compiler/src/ir_core/const_pool.rs b/crates/prometeu-compiler/src/ir_core/const_pool.rs new file mode 100644 index 00000000..2e438712 --- /dev/null +++ b/crates/prometeu-compiler/src/ir_core/const_pool.rs @@ -0,0 +1,40 @@ +use serde::{Deserialize, Serialize}; +use super::ids::ConstId; + +/// Represents a constant value that can be stored in the constant pool. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ConstantValue { + Int(i64), + Float(f64), + String(String), +} + +/// A stable constant pool that handles deduplication and provides IDs. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct ConstPool { + pub constants: Vec, +} + +impl ConstPool { + /// Creates a new, empty constant pool. + pub fn new() -> Self { + Self::default() + } + + /// Inserts a value into the pool if it doesn't already exist. + /// Returns the corresponding `ConstId`. + pub fn insert(&mut self, value: ConstantValue) -> ConstId { + if let Some(pos) = self.constants.iter().position(|c| c == &value) { + ConstId(pos as u32) + } else { + let id = self.constants.len() as u32; + self.constants.push(value); + ConstId(id) + } + } + + /// Retrieves a value from the pool by its `ConstId`. + pub fn get(&self, id: ConstId) -> Option<&ConstantValue> { + self.constants.get(id.0 as usize) + } +} diff --git a/crates/prometeu-compiler/src/ir_core/mod.rs b/crates/prometeu-compiler/src/ir_core/mod.rs index 1994c7c4..86af5cdd 100644 --- a/crates/prometeu-compiler/src/ir_core/mod.rs +++ b/crates/prometeu-compiler/src/ir_core/mod.rs @@ -1,4 +1,5 @@ pub mod ids; +pub mod const_pool; pub mod program; pub mod module; pub mod function; @@ -7,6 +8,7 @@ pub mod instr; pub mod terminator; pub use ids::*; +pub use const_pool::*; pub use program::*; pub use module::*; pub use function::*; diff --git a/crates/prometeu-compiler/src/ir_core/program.rs b/crates/prometeu-compiler/src/ir_core/program.rs index d341526c..4fe30049 100644 --- a/crates/prometeu-compiler/src/ir_core/program.rs +++ b/crates/prometeu-compiler/src/ir_core/program.rs @@ -1,8 +1,10 @@ use serde::{Deserialize, Serialize}; use super::module::Module; +use super::const_pool::ConstPool; -/// A complete PBS program, consisting of multiple modules. +/// A complete PBS program, consisting of multiple modules and a shared constant pool. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Program { + pub const_pool: ConstPool, pub modules: Vec, } diff --git a/crates/prometeu-compiler/tests/backend_tests.rs b/crates/prometeu-compiler/tests/backend_tests.rs new file mode 100644 index 00000000..b046ee9c --- /dev/null +++ b/crates/prometeu-compiler/tests/backend_tests.rs @@ -0,0 +1,42 @@ +use prometeu_compiler::ir::module::{Module, Function}; +use prometeu_compiler::ir::instr::{Instruction, InstrKind}; +use prometeu_compiler::ir::types::Type; +use prometeu_compiler::ir_core::const_pool::ConstantValue; +use prometeu_compiler::backend::emit_module; +use prometeu_compiler::common::files::FileManager; +use prometeu_bytecode::pbc::parse_pbc; +use prometeu_bytecode::pbc::ConstantPoolEntry; + +#[test] +fn test_emit_module_with_const_pool() { + let mut module = Module::new("test".to_string()); + + // Insert constants into IR module + let id_int = module.const_pool.insert(ConstantValue::Int(12345)); + let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string())); + + let function = Function { + name: "main".to_string(), + params: vec![], + return_type: Type::Void, + body: vec![ + Instruction::new(InstrKind::PushConst(id_int), None), + Instruction::new(InstrKind::PushConst(id_str), None), + Instruction::new(InstrKind::Ret, None), + ], + }; + + module.functions.push(function); + + let file_manager = FileManager::new(); + let result = emit_module(&module, &file_manager).expect("Failed to emit module"); + + let pbc = parse_pbc(&result.rom).expect("Failed to parse emitted PBC"); + + // Check constant pool in PBC + // PBC CP has Null at index 0, so our constants should be at 1 and 2 + assert_eq!(pbc.cp.len(), 3); + assert_eq!(pbc.cp[0], ConstantPoolEntry::Null); + assert_eq!(pbc.cp[1], ConstantPoolEntry::Int64(12345)); + assert_eq!(pbc.cp[2], ConstantPoolEntry::String("hello".to_string())); +} diff --git a/crates/prometeu-compiler/tests/const_pool_tests.rs b/crates/prometeu-compiler/tests/const_pool_tests.rs new file mode 100644 index 00000000..466c2ad7 --- /dev/null +++ b/crates/prometeu-compiler/tests/const_pool_tests.rs @@ -0,0 +1,43 @@ +use prometeu_compiler::ir_core::const_pool::{ConstPool, ConstantValue}; +use prometeu_compiler::ir_core::ids::ConstId; + +#[test] +fn test_const_pool_deduplication() { + let mut pool = ConstPool::new(); + + let id1 = pool.insert(ConstantValue::Int(42)); + let id2 = pool.insert(ConstantValue::String("hello".to_string())); + let id3 = pool.insert(ConstantValue::Int(42)); + + assert_eq!(id1, id3); + assert_ne!(id1, id2); + assert_eq!(pool.constants.len(), 2); +} + +#[test] +fn test_const_pool_deterministic_assignment() { + let mut pool = ConstPool::new(); + + let id0 = pool.insert(ConstantValue::Int(10)); + let id1 = pool.insert(ConstantValue::Int(20)); + let id2 = pool.insert(ConstantValue::Int(30)); + + assert_eq!(id0, ConstId(0)); + assert_eq!(id1, ConstId(1)); + assert_eq!(id2, ConstId(2)); +} + +#[test] +fn test_const_pool_serialization() { + let mut pool = ConstPool::new(); + pool.insert(ConstantValue::Int(42)); + pool.insert(ConstantValue::String("test".to_string())); + pool.insert(ConstantValue::Float(3.14)); + + let json = serde_json::to_string_pretty(&pool).unwrap(); + + // Check for deterministic shape in JSON + assert!(json.contains("\"Int\": 42")); + assert!(json.contains("\"String\": \"test\"")); + assert!(json.contains("\"Float\": 3.14")); +} diff --git a/crates/prometeu-compiler/tests/ir_core_tests.rs b/crates/prometeu-compiler/tests/ir_core_tests.rs index d8c839d7..f66c2ef3 100644 --- a/crates/prometeu-compiler/tests/ir_core_tests.rs +++ b/crates/prometeu-compiler/tests/ir_core_tests.rs @@ -3,7 +3,11 @@ use serde_json; #[test] fn test_ir_core_manual_construction() { + let mut const_pool = ConstPool::new(); + const_pool.insert(ConstantValue::String("hello".to_string())); + let program = Program { + const_pool, modules: vec![Module { name: "main".to_string(), functions: vec![Function { @@ -25,6 +29,13 @@ fn test_ir_core_manual_construction() { // Snapshot check for deterministic shape let expected = r#"{ + "const_pool": { + "constants": [ + { + "String": "hello" + } + ] + }, "modules": [ { "name": "main",