pr 03
This commit is contained in:
parent
9dc5290f78
commit
845fc36bd2
@ -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)]));
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
|
||||
40
crates/prometeu-compiler/src/ir_core/const_pool.rs
Normal file
40
crates/prometeu-compiler/src/ir_core/const_pool.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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::*;
|
||||
|
||||
@ -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>,
|
||||
}
|
||||
|
||||
42
crates/prometeu-compiler/tests/backend_tests.rs
Normal file
42
crates/prometeu-compiler/tests/backend_tests.rs
Normal 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()));
|
||||
}
|
||||
43
crates/prometeu-compiler/tests/const_pool_tests.rs
Normal file
43
crates/prometeu-compiler/tests/const_pool_tests.rs
Normal 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"));
|
||||
}
|
||||
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user