diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index 93686dc2..04d7b298 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -79,6 +79,12 @@ impl<'a> BytecodeEmitter<'a> { mapped_const_ids.push(self.add_ir_constant(val)); } + // Map FunctionIds to names for call resolution + let mut func_names = std::collections::HashMap::new(); + for func in &module.functions { + func_names.insert(func.id, func.name.clone()); + } + // --- PHASE 1: Lowering IR to Assembly-like structures --- for function in &module.functions { // Each function starts with a label for its entry point. @@ -146,7 +152,8 @@ impl<'a> BytecodeEmitter<'a> { InstrKind::Label(label) => { asm_instrs.push(Asm::Label(label.0.clone())); } - InstrKind::Call { name, arg_count } => { + InstrKind::Call { func_id, arg_count } => { + let name = func_names.get(func_id).ok_or_else(|| anyhow!("Undefined function ID: {:?}", func_id))?; asm_instrs.push(Asm::Op(OpCode::Call, vec![Operand::Label(name.clone()), Operand::U32(*arg_count)])); } InstrKind::Ret => asm_instrs.push(Asm::Op(OpCode::Ret, vec![])), @@ -154,8 +161,6 @@ impl<'a> BytecodeEmitter<'a> { asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)])); } InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])), - InstrKind::PushScope => asm_instrs.push(Asm::Op(OpCode::PushScope, vec![])), - InstrKind::PopScope => asm_instrs.push(Asm::Op(OpCode::PopScope, vec![])), } let end_idx = asm_instrs.len(); diff --git a/crates/prometeu-compiler/src/backend/lowering.rs b/crates/prometeu-compiler/src/backend/lowering.rs index e69de29b..4eecacd2 100644 --- a/crates/prometeu-compiler/src/backend/lowering.rs +++ b/crates/prometeu-compiler/src/backend/lowering.rs @@ -0,0 +1,62 @@ +use crate::ir; +use crate::ir_core; +use anyhow::Result; + +pub fn lower_program(program: &ir_core::Program) -> Result { + // For now, we just lower the first module as a smoke test + if let Some(core_module) = program.modules.first() { + let mut vm_module = ir::Module::new(core_module.name.clone()); + vm_module.const_pool = program.const_pool.clone(); + + for core_func in &core_module.functions { + let mut vm_func = ir::Function { + id: core_func.id, + name: core_func.name.clone(), + params: vec![], // TODO: lower params + return_type: ir::Type::Null, // TODO: lower return type + body: vec![], + }; + + for block in &core_func.blocks { + // Label for the block + vm_func.body.push(ir::Instruction::new( + ir::InstrKind::Label(ir::Label(format!("block_{}", block.id))), + None, + )); + + for instr in &block.instrs { + match instr { + ir_core::Instr::PushConst(id) => { + vm_func.body.push(ir::Instruction::new( + ir::InstrKind::PushConst(*id), + None, + )); + } + ir_core::Instr::Call(id) => { + vm_func.body.push(ir::Instruction::new( + ir::InstrKind::Call { func_id: *id, arg_count: 0 }, + None, + )); + } + } + } + + match &block.terminator { + ir_core::Terminator::Return => { + vm_func.body.push(ir::Instruction::new(ir::InstrKind::Ret, None)); + } + ir_core::Terminator::Jump(target) => { + vm_func.body.push(ir::Instruction::new( + ir::InstrKind::Jmp(ir::Label(format!("block_{}", target))), + None, + )); + } + } + } + vm_module.functions.push(vm_func); + } + Ok(vm_module) + } else { + anyhow::bail!("No modules in core program") + } +} diff --git a/crates/prometeu-compiler/src/common/spans.rs b/crates/prometeu-compiler/src/common/spans.rs index 41f60b16..591d80a1 100644 --- a/crates/prometeu-compiler/src/common/spans.rs +++ b/crates/prometeu-compiler/src/common/spans.rs @@ -1,4 +1,6 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Span { pub file_id: usize, pub start: u32, diff --git a/crates/prometeu-compiler/src/ir/instr.rs b/crates/prometeu-compiler/src/ir/instr.rs index 1c16d592..82af4900 100644 --- a/crates/prometeu-compiler/src/ir/instr.rs +++ b/crates/prometeu-compiler/src/ir/instr.rs @@ -5,11 +5,11 @@ //! easy to lower into VM-specific bytecode. use crate::common::spans::Span; -use crate::ir_core::ids::ConstId; +use crate::ir_core::ids::{ConstId, FunctionId}; /// An `Instruction` combines an instruction's behavior (`kind`) with its /// source code location (`span`) for debugging and error reporting. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Instruction { pub kind: InstrKind, /// The location in the original source code that generated this instruction. @@ -25,13 +25,13 @@ impl Instruction { /// A `Label` represents a destination for a jump instruction. /// During the assembly phase, labels are resolved into actual memory offsets. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct Label(pub String); /// The various types of operations that can be performed in the IR. /// /// The IR uses a stack-based model, similar to the final Prometeu ByteCode. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum InstrKind { /// Does nothing. Nop, @@ -124,9 +124,9 @@ pub enum InstrKind { JmpIfFalse(Label), /// Defines a location that can be jumped to. Does not emit code by itself. Label(Label), - /// Calls a function by name with the specified number of arguments. + /// Calls a function by ID with the specified number of arguments. /// Arguments should be pushed onto the stack before calling. - Call { name: String, arg_count: u32 }, + Call { func_id: FunctionId, arg_count: u32 }, /// Returns from the current function. The return value (if any) should be on top of the stack. Ret, @@ -136,9 +136,4 @@ pub enum InstrKind { Syscall(u32), /// Special instruction to synchronize with the hardware frame clock. FrameSync, - - /// Internal: Pushes a new lexical scope (used for variable resolution). - PushScope, - /// Internal: Pops the current lexical scope. - PopScope, } diff --git a/crates/prometeu-compiler/src/ir/mod.rs b/crates/prometeu-compiler/src/ir/mod.rs index cb73bc68..2cc408ed 100644 --- a/crates/prometeu-compiler/src/ir/mod.rs +++ b/crates/prometeu-compiler/src/ir/mod.rs @@ -3,6 +3,6 @@ pub mod module; pub mod instr; pub mod validate; -pub use instr::Instruction; -pub use module::Module; +pub use instr::{Instruction, InstrKind, Label}; +pub use module::{Module, Function, Global, Param}; pub use types::Type; diff --git a/crates/prometeu-compiler/src/ir/module.rs b/crates/prometeu-compiler/src/ir/module.rs index 0ab6e2e9..737576bb 100644 --- a/crates/prometeu-compiler/src/ir/module.rs +++ b/crates/prometeu-compiler/src/ir/module.rs @@ -7,10 +7,12 @@ use crate::ir::instr::Instruction; use crate::ir::types::Type; use crate::ir_core::const_pool::ConstPool; +use crate::ir_core::ids::FunctionId; +use serde::{Deserialize, Serialize}; /// A `Module` is the top-level container for a compiled program or library. /// It contains a collection of global variables, functions, and a constant pool. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Module { /// The name of the module (usually derived from the project name). pub name: String, @@ -26,8 +28,10 @@ pub struct Module { /// /// Functions consist of a signature (name, parameters, return type) and a body /// which is a flat list of IR instructions. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Function { + /// The unique identifier of the function. + pub id: FunctionId, /// The unique name of the function. pub name: String, /// The list of input parameters. @@ -39,7 +43,7 @@ pub struct Function { } /// A parameter passed to a function. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Param { /// The name of the parameter (useful for debugging and symbols). pub name: String, @@ -48,7 +52,7 @@ pub struct Param { } /// A global variable accessible by any function in the module. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Global { /// The name of the global variable. pub name: String, diff --git a/crates/prometeu-compiler/src/ir/types.rs b/crates/prometeu-compiler/src/ir/types.rs index 4eecdf6e..00f24e52 100644 --- a/crates/prometeu-compiler/src/ir/types.rs +++ b/crates/prometeu-compiler/src/ir/types.rs @@ -1,4 +1,6 @@ -#[derive(Debug, Clone, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Type { Any, Null, diff --git a/crates/prometeu-compiler/tests/backend_tests.rs b/crates/prometeu-compiler/tests/backend_tests.rs index b046ee9c..3c5731c0 100644 --- a/crates/prometeu-compiler/tests/backend_tests.rs +++ b/crates/prometeu-compiler/tests/backend_tests.rs @@ -1,6 +1,7 @@ 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::ids::FunctionId; use prometeu_compiler::ir_core::const_pool::ConstantValue; use prometeu_compiler::backend::emit_module; use prometeu_compiler::common::files::FileManager; @@ -16,6 +17,7 @@ fn test_emit_module_with_const_pool() { let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string())); let function = Function { + id: FunctionId(0), name: "main".to_string(), params: vec![], return_type: Type::Void, diff --git a/crates/prometeu-compiler/tests/vm_ir_tests.rs b/crates/prometeu-compiler/tests/vm_ir_tests.rs new file mode 100644 index 00000000..a5f57403 --- /dev/null +++ b/crates/prometeu-compiler/tests/vm_ir_tests.rs @@ -0,0 +1,122 @@ +use prometeu_compiler::ir::*; +use prometeu_compiler::ir_core::ids::{ConstId, FunctionId}; +use prometeu_compiler::ir_core::const_pool::{ConstPool, ConstantValue}; +use serde_json; + +#[test] +fn test_vm_ir_serialization() { + let mut const_pool = ConstPool::new(); + const_pool.insert(ConstantValue::String("Hello VM".to_string())); + + let module = Module { + name: "test_module".to_string(), + const_pool, + functions: vec![Function { + id: FunctionId(1), + name: "main".to_string(), + params: vec![], + return_type: Type::Null, + body: vec![ + Instruction::new(InstrKind::PushConst(ConstId(0)), None), + Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None), + Instruction::new(InstrKind::Ret, None), + ], + }], + globals: vec![], + }; + + let json = serde_json::to_string_pretty(&module).unwrap(); + + // Snapshot check + let expected = r#"{ + "name": "test_module", + "const_pool": { + "constants": [ + { + "String": "Hello VM" + } + ] + }, + "functions": [ + { + "id": 1, + "name": "main", + "params": [], + "return_type": "Null", + "body": [ + { + "kind": { + "PushConst": 0 + }, + "span": null + }, + { + "kind": { + "Call": { + "func_id": 2, + "arg_count": 1 + } + }, + "span": null + }, + { + "kind": "Ret", + "span": null + } + ] + } + ], + "globals": [] +}"#; + assert_eq!(json, expected); +} + +#[test] +fn test_lowering_smoke() { + use prometeu_compiler::ir_core; + use prometeu_compiler::backend::lowering::lower_program; + + let mut const_pool = ir_core::ConstPool::new(); + const_pool.insert(ir_core::ConstantValue::Int(42)); + + let program = ir_core::Program { + const_pool, + modules: vec![ir_core::Module { + name: "test_core".to_string(), + functions: vec![ir_core::Function { + id: FunctionId(10), + name: "start".to_string(), + blocks: vec![ir_core::Block { + id: 0, + instrs: vec![ + ir_core::Instr::PushConst(ConstId(0)), + ], + terminator: ir_core::Terminator::Return, + }], + }], + }], + }; + + let vm_module = lower_program(&program).expect("Lowering failed"); + + assert_eq!(vm_module.name, "test_core"); + assert_eq!(vm_module.functions.len(), 1); + let func = &vm_module.functions[0]; + assert_eq!(func.name, "start"); + assert_eq!(func.id, FunctionId(10)); + + // Check if instructions were lowered (label + pushconst + ret) + assert_eq!(func.body.len(), 3); + match &func.body[0].kind { + InstrKind::Label(Label(l)) => assert!(l.contains("block_0")), + _ => panic!("Expected label"), + } + match &func.body[1].kind { + InstrKind::PushConst(id) => assert_eq!(id.0, 0), + _ => panic!("Expected PushConst"), + } + match &func.body[2].kind { + InstrKind::Ret => (), + _ => panic!("Expected Ret"), + } +}