This commit is contained in:
Nilton Constantino 2026-01-27 13:29:38 +00:00
parent 9dc5290f78
commit 845fc36bd2
No known key found for this signature in database
9 changed files with 169 additions and 19 deletions

View File

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

View File

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

View File

@ -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<Function>,
/// 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(),
}

View File

@ -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<ConstantValue>,
}
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)
}
}

View File

@ -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::*;

View File

@ -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<Module>,
}

View File

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

View File

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

View File

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