dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
9 changed files with 216 additions and 22 deletions
Showing only changes of commit 8240785160 - Show all commits

View File

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

View File

@ -0,0 +1,62 @@
use crate::ir;
use crate::ir_core;
use anyhow::Result;
pub fn lower_program(program: &ir_core::Program) -> Result<ir::Module> {
// 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")
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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